import * as React from 'react';
import * as ReactRouter from 'react-router';

/**
 * Defines the API accessible from pilets.
 */
export interface PiletApi extends EventEmitter, PiletCustomApi, PiletCoreApi {
  /**
   * Gets the metadata of the current pilet.
   */
  meta: PiletMetadata;
}

/**
 * The emitter for Piral app shell events.
 */
export interface EventEmitter {
  /**
   * Attaches a new event listener.
   * @param type The type of the event to listen for.
   * @param callback The callback to trigger.
   */
  on<K extends keyof PiralEventMap>(type: K, callback: Listener<PiralEventMap[K]>): EventEmitter;
  /**
   * Attaches a new event listener that is removed once the event fired.
   * @param type The type of the event to listen for.
   * @param callback The callback to trigger.
   */
  once<K extends keyof PiralEventMap>(type: K, callback: Listener<PiralEventMap[K]>): EventEmitter;
  /**
   * Detaches an existing event listener.
   * @param type The type of the event to listen for.
   * @param callback The callback to trigger.
   */
  off<K extends keyof PiralEventMap>(type: K, callback: Listener<PiralEventMap[K]>): EventEmitter;
  /**
   * Emits a new event with the given type.
   * @param type The type of the event to emit.
   * @param arg The payload of the event.
   */
  emit<K extends keyof PiralEventMap>(type: K, arg: PiralEventMap[K]): EventEmitter;
}

/**
 * Custom Pilet API parts defined outside of piral-core.
 */
export interface PiletCustomApi extends AuthApi, EnvironmentApi, HomeApi, RegistrationInfoApi, NavigationApi, NotificationApi, ProviderApi, NavigationResolverApi, IndicatorApi, TitleApi {}

/**
 * Defines the Pilet API from piral-core.
 * This interface will be consumed by pilet developers so that their pilet can interact with the piral instance.
 */
export interface PiletCoreApi {
  /**
   * Gets a shared data value.
   * @param name The name of the data to retrieve.
   */
  getData<TKey extends string>(name: TKey): SharedData[TKey];
  /**
   * Sets the data using a given name. The name needs to be used exclusively by the current pilet.
   * Using the name occupied by another pilet will result in no change.
   * @param name The name of the data to store.
   * @param value The value of the data to store.
   * @param options The optional configuration for storing this piece of data.
   * @returns True if the data could be set, otherwise false.
   */
  setData<TKey extends string>(name: TKey, value: SharedData[TKey], options?: DataStoreOptions): boolean;
  /**
   * Registers a route for predefined page component.
   * The route needs to be unique and can contain params.
   * Params are following the path-to-regexp notation, e.g., :id for an id parameter.
   * @param route The route to register.
   * @param Component The component to render the page.
   * @param meta The optional metadata to use.
   */
  registerPage(route: string, Component: AnyComponent<PageComponentProps>, meta?: PiralPageMeta): RegistrationDisposer;
  /**
   * Unregisters the page identified by the given route.
   * @param route The route that was previously registered.
   */
  unregisterPage(route: string): void;
  /**
   * Registers an extension component with a predefined extension component.
   * The name must refer to the extension slot.
   * @param name The global name of the extension slot.
   * @param Component The component to be rendered.
   * @param defaults Optionally, sets the default values for the expected data.
   */
  registerExtension<TName>(name: TName extends string ? TName : string, Component: AnyExtensionComponent<TName>, defaults?: Partial<ExtensionParams<TName>>): RegistrationDisposer;
  /**
   * Unregisters a global extension component.
   * Only previously registered extension components can be unregistered.
   * @param name The name of the extension slot to unregister from.
   * @param Component The registered extension component to unregister.
   */
  unregisterExtension<TName>(name: TName extends string ? TName : string, Component: AnyExtensionComponent<TName>): void;
  /**
   * React component for displaying extensions for a given name.
   * @param props The extension's rendering props.
   * @return The created React element.
   */
  Extension<TName>(props: ExtensionSlotProps<TName>): React.ReactElement | null;
  /**
   * Renders an extension in a plain DOM component.
   * @param element The DOM element or shadow root as a container for rendering the extension.
   * @param props The extension's rendering props.
   * @return The disposer to clear the extension.
   */
  renderHtmlExtension<TName>(element: HTMLElement | ShadowRoot, props: ExtensionSlotProps<TName>): Disposable;
}

/**
 * Describes the metadata of a pilet available in its API.
 */
export interface PiletMetadata {
  /**
   * The name of the pilet, i.e., the package id.
   */
  name: string;
  /**
   * The version of the pilet. Should be semantically versioned.
   */
  version: string;
  /**
   * Provides the version of the specification for this pilet.
   */
  spec: string;
  /**
   * Provides some custom metadata for the pilet.
   */
  custom?: any;
  /**
   * Optionally indicates the global require reference, if any.
   */
  requireRef?: string;
  /**
   * Additional shared dependencies from the pilet.
   */
  dependencies: Record<string, string>;
  /**
   * Provides some configuration to be used in the pilet.
   */
  config: Record<string, any>;
  /**
   * The URL of the main script of the pilet.
   */
  link: string;
  /**
   * The base path to the pilet. Can be used to make resource requests
   * and override the public path.
   */
  basePath: string;
}

/**
 * Listener for Piral app shell events.
 */
export interface Listener<T> {
  /**
   * Receives an event of type T.
   */
  (arg: T): void;
}

/**
 * The map of known Piral app shell events.
 */
export interface PiralEventMap extends PiralCustomEventMap {
  "unload-pilet": PiralUnloadPiletEvent;
  [custom: string]: any;
  "store-data": PiralStoreDataEvent;
}

export interface AuthApi {
  /**
   * Gets the currently authenticated user, if any.
   */
  getToken(): Promise<User | undefined>;
  /**
   * Checks if the user has one of the `requiredPermissions`.
   */
  checkPermission(requiredPermissions: UserPermissions): Promise<boolean>;
}

export interface EnvironmentApi {
  /**
   * Returns current portal environment
   */
  getEnvironment(): PortalConfiguration;
}

export interface HomeApi {
  /**
   * Registers a tile with preferences.
   * The name has to be unique within the Current pilet.
   * @param data HomeTileData or AdminTileData object with the details of the tile.
   * @param registerNavItem if item needs to be added to navigation registry, defaults to true
   * @example
   * app.registerTile({
   *  kind: 'home',
   *  name: 'service',
   *  path: '/service-packages',
   *  label: 'Service Packages',
   *  icon: require('./images/service-packages.svg'),
   *  type: 'large',
   * });
   * @example
   * app.registerTile({
   *  kind: 'settings',
   *  name: 'service',  
   *  groupName: 'Users',
   *  path: '/users/roles',
   *  label: 'Roles',
   *  icon: require('./images/roles.svg'),
   * })
   */
  registerTile(data: AnyTile, registerNavItem?: boolean): RegistrationDisposer;
  /**
   * Unregisters a tile known by the given name.
   * Only previously registered tiles can be unregistered.
   * @param name The name of the tile to unregister.
   */
  unregisterTile(name: string): void;
  /**
   * Use this to set a function that will be called to determine the order of the tiles on the home station.
   * @param sorter Sorter function.
   */
  setHomeTileSorter(sorter: TileSorter<HomeTileData>): void;
  /**
   * Use this to set a custom home page, which will replace the default home page when set.
   * @param content The custom home page content.
   */
  registerHomePage(content: React.ComponentType<HomeComponentProps>): void;
  /**
   * Creates a connector for wrapping home with data relations.
   */
  createHomeConnector(): HomeStateContainer;
}

export interface RegistrationInfoApi {
  /**
   * Registered page components for the router
   * @return Key value pair with registered route as key and it's page component as value
   */
  getRegisteredPages: GetRegisteredPages;
  /**
   * Registered extension components for extension slots
   * @return Key value pair with extension slot name as key and array of it's components as a value
   */
  getRegisteredExtensions: GetRegisteredExtensions;
  /**
   * @return metadata for currently loaded pilets
   */
  getRegisteredPilets: GetRegisteredPilets;
  /**
   * Helps retrieving data from data store, that is set by `app.setData`
   * Unlike `app.getData`, this method will wait for the data to be set if it is not
   * available immediately and then execute the callback with the data
   * @param dataKey key of the data to retrieve
   * @param callback callback handling retrieved data
   * @returns a promise with the result of callback
   */
  getDataHelper<TData, TReturn>(dataKey: string, callback: GetDataCallback<TData, TReturn>): Promise<TReturn>;
}

export interface NavigationApi {
  /**
   * Registers navigation panel item
   * @param item Contains all details for createing navigation item
   * @example
   * app.registerNavigationItem({
   *    name: 'service-management',
   *    categoryName: 'Settings',
   *    path: '/settings/service-management',
   *    label: 'Service Management',
   *  });
   *  app.registerNavigationItem({
   *    name: 'service-management-feature',
   *    parentName: 'service-management',
   *    categoryName: 'Settings',
   *    path: '/settings/service-management/service-feature',
   *    label: 'Service Management',
   *  });
   */
  registerNavigationItem(item: RegisterNavigationItem): () => void;
  /**
   * Registers navigation settings item under admin settings category in navigation panel
   * @param item Contains all details for creating navigation settings item
   * @example
   * app.registerNavigationSettingsItem({
   *    name: 'service-management',
   *    path: '/settings/service-management',
   *    label: 'Service Management',
   *  });
   */
  registerNavigationSettingsItem(item: RegisterNavigationSettingsItem): () => void;
  /**
   * Removes registered navigation item
   * @param name Unique name of the navigation item, that was provided on route registration
   */
  unregisterNavigationItem(name: string): void;
  /**
   * Creates connector for wrapping component with navigation state data relations
   * @example
   * ```ts
   *  const connect = app.createNavigationConnector();
   *  connect(({ items, itemsTransformer, treeTransformer }) => (
   *    <MyComponent
   *      items={items}
   *      itemsTransformer={itemsTransformer}
   *      treeTransformer={treeTransformer}
   *    />
   *  ));
   * ```
   */
  createNavigationConnector(): NavigationConnector;
  /**
   * Registers provided navigation item transformation function 
   * `transformer` function receives flat array of registered navigation items
   * Use this to alter or re-order registered navigation items
   * @param transformer function that alters navigation registry items
   * @example
   * app.setNavigationItemsTransformer(
   *  (navItems: NavigationItem[]): NavigationItem[] => {
   *    return navItems.map((item) => {
   *      if (item.name.includes('playground')) {
   *        item.categoryName = 'Playground';
   *        item.label = item.name.replace(/playground|-/g, ' ');
   *        item.parentName = undefined;
   *      }
   *      return item;
   *    });
   *  },
   * );
   */
  setNavigationItemsTransformer(transformer: NavigationTransformer<NavigationItem>): void;
  /**
   * Registers provided navigation tree transformation function 
   * `transformer` function receives navigation item tree created from registered navigation items
   * Use this to alter or re-order navigation panel categories or their items
   * @param transformer Function that alters navigation panel item order
   * @example
   * app.setNavigationTreeTransformer(
   *  (navTree: NavigationPanelCategory[]): NavigationPanelCategory[] => {
   *    return navTree.sort((a, b) =>
   *      a.categoryName.localeCompare(b.categoryName),
   *    );
   *  },
   * );
   */
  setNavigationTreeTransformer(transformer: NavigationTransformer<NavigationPanelCategory>): void;
}

export interface NotificationApi {
  /**
   * Displays a toast notification
   * @param notification Notification to be shown
   * @returns Id of the toast, used for updating or dismissing the notification
   * @example
   *  const toastId = app.showNotification({
   *    title: 'Notification shown',
   *    body: 'Hello world',
   *    options: { type: 'error', autoClose: false },
   *  });
   */
  showNotification: ShowNotification;
  /**
   * Updates a toast notification
   * @param id toastId to be updated (returned from 'show' method call)
   * @param notification Updated notification
   * @example
   *  app.updateNotification(toastId, {
   *    title: 'Notification updated',
   *    body: 'Hello world',
   *    options: { autoClose: 5000 },
   *  });
   */
  updateNotification: UpdateNotification;
  /**
   * Dismisses a toast notification
   * @param id toastId to be dismissed (returned from 'show' method call)
   * @example
   * app.dismissNotification(toastId);
   */
  dismissNotification: DismissNotification;
}

export interface ProviderApi {
  /**
   * Creates a provider object using the 'type' property. Assigns data and converter args to an array of objects as its value. If the 'type' already exists, the data will be appended to the array.
   * @param type provider name
   * @param data provider data
   * @param converter function used to transformation
   * @example
   * // new provider
   * app.addProvider<Example>(
   *  'fast-provider',
   *  data: { type: 'movie' });
   * // appends object to 'fast-provider'array
   * app.addProvider(
   *  'fast-provider',,
   *  data: { type: 'episode' });
   */
  addProvider: ProviderRegistration;
  /**
   * Returns an array of data associated with a specific provider
   * @param type Unique value of the provider 'type'
   * @example
   * app.getProviders('fast-provider');
   */
  getProviders: GetProviders;
}

export interface NavigationResolverApi {
  /**
   * @deprecated Use `setRouteResolver` instead to register a resolver function for a station
   * @param data
   * @example
   * ```ts
   * app.registerRouteResolver({
   *  station: 'movie-details',
   *  resolver: (dynamicRouteSegments?: string[]) => {
   *     return dynamicRouteSegments?.length
   *      ? `/movies/${dynamicRouteSegments[0]}`
   *       : undefined;
   *   },
   * });
   * ```
   */
  registerRouteResolver(data: ResolverData): void;
  /**
   * Register a resolver function for a station
   * @param station Name of the station used when registering the resolver with `registerRouteResolver`
   * @param resolver Route resolver function
   * @example
   * ```ts
   *   app.setRouteResolver(
   *     'movie-details',
   *     (dynamicRouteSegments?: { id: string }) =>
   *       dynamicRouteSegments ? `/movies/${dynamicRouteSegments.id}` : undefined,
   *   );
   * ```
   * @note Merge Typescript `@axinom/mosaic-portal` module type declaration
   * with your custom `RegisterRouteResolver` type declaration for `setRouteResolver`
   * @example
   * ```ts
   * declare module '@axinom/mosaic-portal' {
   *  interface RegisterRouteResolver {
   *  ('movie-details', (dynamicRouteSegments?: {id: string}) => string | undefined): void;
   * ```
   */
  setRouteResolver: RegistrationFunction;
  /**
   * Resolves a route for a given station
   * @param station Name of the station used when registering the resolver with `registerRouteResolver`
   * @param dynamicRouteSegments Dynamic route segments.
   * It is either a string containing entity id or an object
   * containing key-value pairs of dynamic route segments.
   * The keys must match the names of the dynamic route segment used when registering the route.
   * @returns Resolved route
   * @example
   * ```ts
   *  const route = app.resolveRoute('movie-details', {id: '123'});
   * ```
   * @note Merge Typescript `@axinom/mosaic-portal` module type declaration
   * with your custom `ResolverFunction` type declaration for `resolveRoute` with types
   * matching your dynamic route segments
   * @example
   * ```ts
   * declare module '@axinom/mosaic-portal' {
   *  interface ResolverFunction {
   *  ('movie-details', dynamicRouteSegments?: {id: string}): void;
   * ```
   */
  resolveRoute: ResolverFunction;
  /**
   * Returns resolvers from navigation registry
   * @returns All registered route resolvers
   */
  getAllRouteResolvers(): NavigationResolverRegistryState["navigationResolvers"];
}

export interface IndicatorApi {
  /**
   * Adds an indicator to the application header.
   * @param content Content for the indicator.
   * @example
   * ```ts
   * app.addIndicator(<div>Saving...</div>);
   * ```
   */
  addIndicator(content: React.ReactNode): IndicatorId;
  /**
   * Removes the indicator in the header.
   */
  removeIndicator(id: IndicatorId): void;
  /**
   * Updates the indicator in the header.
   */
  updateIndicator(id: IndicatorId, content: React.ReactNode): void;
  /**
   * Sets the save indicator state.
   * @deprecated The saving button is now part of the FormStation component.
   * Make sure you're using the latest version of the UI library which will
   * manage the saving button/indicator state for you.
   */
  setSaveIndicator(state: SaveIndicatorType): void;
}

export interface TitleApi {
  setTitle(content?: string): void;
}

/**
 * Defines the shape of the data store for storing shared data.
 */
export interface SharedData<TValue = any> {
  [key: string]: TValue;
}

/**
 * Defines the options to be used for storing data.
 */
export type DataStoreOptions = DataStoreTarget | CustomDataStoreOptions;

/**
 * Possible shapes for a component.
 */
export type AnyComponent<T> = React.ComponentType<T> | FirstParametersOf<ComponentConverters<T>>;

/**
 * The props used by a page component.
 */
export interface PageComponentProps<T extends {
  [K in keyof T]?: string;
} = {}, S = any> extends RouteBaseProps<T, S> {
  /**
   * The meta data registered with the page.
   */
  meta: PiralPageMeta;
  /**
   * The children of the page.
   */
  children: React.ReactNode;
}

/**
 * The meta data registered for a page.
 */
export interface PiralPageMeta extends PiralCustomPageMeta {}

/**
 * The shape of an implicit unregister function.
 */
export interface RegistrationDisposer {
  /**
   * Cleans up the previous registration.
   */
  (): void;
}

/**
 * Shorthand for the definition of an extension component.
 */
export type AnyExtensionComponent<TName> = TName extends keyof PiralExtensionSlotMap ? AnyComponent<ExtensionComponentProps<TName>> : TName extends string ? AnyComponent<ExtensionComponentProps<any>> : AnyComponent<ExtensionComponentProps<TName>>;

/**
 * Gives the extension params shape for the given extension slot name.
 */
export type ExtensionParams<TName> = TName extends keyof PiralExtensionSlotMap ? PiralExtensionSlotMap[TName] : TName extends string ? any : TName;

/**
 * The props for defining an extension slot.
 */
export type ExtensionSlotProps<TName = string> = BaseExtensionSlotProps<TName extends string ? TName : string, ExtensionParams<TName>>;

/**
 * Can be implemented by functions to be used for disposal purposes.
 */
export interface Disposable {
  /**
   * Disposes the created resource.
   */
  (): void;
}

/**
 * Custom events defined outside of piral-core.
 */
export interface PiralCustomEventMap {}

/**
 * Gets fired when a pilet gets unloaded.
 */
export interface PiralUnloadPiletEvent {
  /**
   * The name of the pilet to be unloaded.
   */
  name: string;
}

/**
 * Gets fired when a data item gets stored in piral.
 */
export interface PiralStoreDataEvent<TValue = any> {
  /**
   * The name of the item that was stored.
   */
  name: string;
  /**
   * The storage target of the item.
   */
  target: string;
  /**
   * The value that was stored.
   */
  value: TValue;
  /**
   * The owner of the item.
   */
  owner: string;
  /**
   * The expiration of the item.
   */
  expires: number;
}

export interface User {
  id: string;
  name: string;
  email: string;
  profilePictureUrl: string;
  token: UserToken;
}

export type UserPermissions = UserToken["permissions"];

export interface PortalConfiguration {
  tenantId: string;
  environmentId: string;
  microFrontendServiceEndpoint: string;
  idServiceAuthEndpoint: string;
}

export type AnyTile = HomeTileData | SettingsTileData;

export interface TileSorter<T extends AnyTile> {
  (tiles: Array<T>): Array<T>;
}

/**
 * Data item for Dashboard Tile
 */
export interface HomeTileData extends LandingPageItem {
  /**
   * Type Tag
   */
  kind: "home";
  /**
   * Unique name of the Home Tile. This value needs to be unique for the Pilet.
   */
  name?: string;
}

export interface HomeComponentProps {
  /**
   * Home Tile data currently available in the state.
   */
  landingPageData?: Array<HomeTileData>;
  /**
   * Extension component from the PiletApi, used to render registered extension slots.
   */
  ExtensionComponent: PiletApi["Extension"];
}

export interface HomeStateContainer {
  /**
   * State container connector function for wrapping a component. TProps generic can be used to define custom props for the component.
   * @param component The component to connect by providing a state.
   */
  <TProps>(component: React.ComponentType<TProps & HomeStateConnectorProps>): React.FC<TProps>;
}

export type GetRegisteredPages = () => Dict<PageRegistration>;

export type GetRegisteredExtensions = () => Dict<Array<ExtensionRegistration>>;

export type GetRegisteredPilets = () => Array<PiletMetadata>;

/**
 * Callback handling retrieved data
 * @param data data retreived from data store
 */
export type GetDataCallback<TData, TReturn> = (data: TData) => TReturn;

export interface RegisterNavigationItem extends Omit<NavigationPanelItem, "items" | "name"> {
  /**
   * Title of category under which navigation item will be displayed
   */
  categoryName: string;
  /**
   * Unique name of the navigation item.
   */
  name?: string;
  /**
   * Name of parent navigation item for adding navigation item as nested child menu item (2nd navigation level)
   */
  parentName?: string;
}

export type RegisterNavigationSettingsItem = Omit<RegisterNavigationItem, "parentName" | "categoryName">;

export interface NavigationConnector {
  /**
   * Connector function for wraping a component
   * @param component The component to connet by providing navigation state properties
   */
  <TProps>(component: React.ComponentType<TProps & NavigationConnectorProps>): React.FC<TProps>;
}

export interface NavigationTransformer<T> {
  (items: Array<T>): Array<T>;
}

export interface NavigationItem extends RegisterNavigationItem {
  /**
   * Unique name of the navigation item provided on registration (auto generated in case not provided)
   */
  readonly name: string;
  /**
   * Name of the pilet registering navigation item
   */
  pilet: string;
}

export interface NavigationPanelCategory {
  /**
   * Name of the category under which navigation items will be listed
   */
  categoryName: string;
  /**
   * Navigation items
   */
  items: Array<NavigationPanelItem>;
}

export type ShowNotification = (notification: Notification) => NotificationId;

export type UpdateNotification = (id: NotificationId, notification: Notification) => void;

export type DismissNotification = (id: NotificationId) => void;

export interface ProviderRegistration {
  (type: never, data: object): void;
}

export interface GetProviders<T = object> {
  (type: string): Array<T>;
}

/**
 * Data item for registering a resolver
 */
export interface ResolverData {
  /**
   * Name of the station, has to be unique among the workflows
   */
  station: string;
  /**
   * Route resolver function
   */
  resolver: NavigationResolver;
}

/**
 * Registration function for registering a resolver
 */
export interface RegistrationFunction {
  (station: never, resolver: NavigationResolver): void;
}

/**
 * Resolver function for resolving a route
 */
export interface ResolverFunction {
  (station: never, dynamicRouteSegments?: Record<string, string> | string): string | undefined;
}

/**
 * Represents the state of the navigation resolver registry
 */
export interface NavigationResolverRegistryState {
  /**
   * The registered navigation resolvers
   * @example
   * ```ts
   * {
   *   'movie-details': (dynamicRouteSegments?: string[]) => dynamicRouteSegments?.length
   *     ? `/movies/${dynamicRouteSegments[0]}`
   *     : undefined
   * },
   * ```
   */
  navigationResolvers: {
    [key: string]: NavigationResolver;
  };
}

export type IndicatorId = number;

export enum SaveIndicatorType {
  Saving = "saving",
  Inactive = "inactive",
  Dirty = "dirty",
}

/**
 * Defines the potential targets when storing data.
 */
export type DataStoreTarget = "memory" | "local" | "remote";

/**
 * Defines the custom options for storing data.
 */
export interface CustomDataStoreOptions {
  /**
   * The target data store. By default the data is only stored in memory.
   */
  target?: DataStoreTarget;
  /**
   * Optionally determines when the data expires.
   */
  expires?: "never" | Date | number;
}

export type FirstParametersOf<T> = {
  [K in keyof T]: T[K] extends (arg: any) => any ? FirstParameter<T[K]> : never;
}[keyof T];

/**
 * Mapping of available component converters.
 */
export interface ComponentConverters<TProps> extends PiralCustomComponentConverters<TProps> {
  /**
   * Converts the HTML component to a framework-independent component.
   * @param component The vanilla JavaScript component to be converted.
   */
  html(component: HtmlComponent<TProps>): ForeignComponent<TProps>;
}

/**
 * The props that every registered page component obtains.
 */
export interface RouteBaseProps<UrlParams extends {
  [K in keyof UrlParams]?: string;
} = {}, UrlState = any> extends ReactRouter.RouteComponentProps<UrlParams, {}, UrlState>, BaseComponentProps {}

/**
 * Custom meta data to include for pages.
 */
export interface PiralCustomPageMeta extends BreadCrumbMeta, PermissionMeta {}

/**
 * The props of an extension component.
 */
export interface ExtensionComponentProps<T> extends BaseComponentProps {
  /**
   * The provided parameters for showing the extension.
   */
  params: T extends keyof PiralExtensionSlotMap ? PiralExtensionSlotMap[T] : T extends string ? any : T;
  /**
   * The optional children to receive, if any.
   */
  children?: React.ReactNode;
}

/**
 * The mapping of the existing (known) extension slots.
 */
export interface PiralExtensionSlotMap extends PiralCustomExtensionSlotMap {}

/**
 * The basic props for defining an extension slot.
 */
export interface BaseExtensionSlotProps<TName, TParams> {
  /**
   * The children to transport, if any.
   */
  children?: React.ReactNode;
  /**
   * Defines what should be rendered when no components are available
   * for the specified extension.
   */
  empty?(): React.ReactNode;
  /**
   * Determines if the `render` function should be called in case no
   * components are available for the specified extension.
   * 
   * If true, `empty` will be called and returned from the slot.
   * If false, `render` will be called with the result of calling `empty`.
   * The result of calling `render` will then be returned from the slot.
   */
  emptySkipsRender?: boolean;
  /**
   * Defines the order of the components to render.
   * May be more convient than using `render` w.r.t. ordering extensions
   * by their supplied metadata.
   * @param extensions The registered extensions.
   * @returns The ordered extensions.
   */
  order?(extensions: Array<ExtensionRegistration>): Array<ExtensionRegistration>;
  /**
   * Defines how the provided nodes should be rendered.
   * @param nodes The rendered extension nodes.
   * @returns The rendered nodes, i.e., an ReactElement.
   */
  render?(nodes: Array<React.ReactNode>): React.ReactElement<any, any> | null;
  /**
   * The custom parameters for the given extension.
   */
  params?: TParams;
  /**
   * The name of the extension to render.
   */
  name: TName;
}

export interface UserToken {
  accessToken: string;
  expiresInSeconds: number;
  permissions: {
    [serviceId: string]: Array<string>;
  };
  tags: Array<string>;
}

/**
 * Data item for Settings page
 */
export interface SettingsTileData extends HubItem {
  /**
   * Type Tag
   */
  kind: "settings";
  /**
   * Unique name of the Home Tile. This value needs to be unique for the Pilet.
   */
  name?: string;
}

export interface LandingPageItem {
  /**
   * url path
   */
  path: string;
  /**
   * Tile label
   */
  label: string;
  /**
   * Icon location or an react element of svg icon.
   * if an react element of svg is passed, the correct style for two types ("small" and "large") will be applied accordingly
   * The styles which don't want to be affected based on the tile type can be passed as inline styles.
   */
  icon: string | React.ReactNode;
  /**
   * Tile type
   */
  type: "small" | "large";
  /**
   * Tile is disabled or enabled, default is false
   */
  disabled?: boolean;
  /**
   * Subtitle resolver
   */
  subtitle?: ValueOrOnDemand;
}

export interface HomeStateConnectorProps {
  /**
   * Home Tile data currently available in the state.
   */
  homeTileData?: Array<HomeTileData>;
  /**
   * Settings Tile data currently available in the state.
   */
  settingsTileData?: Array<SettingsTileData>;
  /**
   * The tile sorter function currently set in the state, which can be used to determine the order of the tiles on the home station. If not set, the default sorting will be alphabetical based on the label.
   */
  tileSorter?: TileSorter<HomeTileData>;
  /**
   * The custom home page component currently set in the state, which will replace the default home page when set. The component will receive the HomeComponentProps as props.
   */
  customHomePage?: React.ComponentType<HomeComponentProps>;
}

export type Dict<T> = Record<string, T>;

/**
 * The interface modeling the registration of a pilet page component.
 */
export interface PageRegistration extends BaseRegistration {
  /**
   * The registered page component.
   */
  component: WrappedComponent<PageComponentProps>;
  /**
   * The page's associated metadata.
   */
  meta: PiralPageMeta;
}

/**
 * The interface modeling the registration of a pilet extension component.
 */
export interface ExtensionRegistration extends BaseRegistration {
  /**
   * The wrapped registered extension component.
   */
  component: WrappedComponent<ExtensionComponentProps<string>>;
  /**
   * The original extension component that has been registered.
   */
  reference: any;
  /**
   * The default params (i.e., meta) of the extension.
   */
  defaults: any;
}

export interface NavigationPanelItem {
  /**
   * Unique name of the navigation item provided on registration (auto generated in case not provided)
   */
  readonly name: string;
  /**
   * Menu item label text
   */
  label: string;
  /**
   * Icon displayed with label
   */
  icon?: string | React.ReactNode;
  /**
   * URL path
   */
  path?: string;
  /**
   * Child navigation items, nested beneath drop down
   */
  items?: Array<NavigationPanelItem>;
}

export interface NavigationConnectorProps {
  /**
   * Registered navigation items
   */
  items: Array<NavigationItem>;
  /**
   * registered navigation registry item transformer
   */
  itemsTransformer?: NavigationTransformer<NavigationItem>;
  /**
   * Registered navigation tree transformer
   */
  treeTransformer?: NavigationTransformer<NavigationPanelCategory>;
}

export interface Notification {
  /**
   * Title shown in toast messages header
   */
  title: string;
  /**
   * Optional body of the notification
   */
  body?: NotificationBody;
  /**
   * Additional options for the notification
   */
  options?: NotificationOptions;
}

export type NotificationId = string | number;

/**
 * Navigation resolver function for resolver registration
 */
export type NavigationResolver = (dynamicRouteSegments?: Record<string, string> | string) => string | undefined;

export type FirstParameter<T extends (arg: any) => any> = T extends (arg: infer P) => any ? P : never;

/**
 * Custom component converters defined outside of piral-core.
 */
export interface PiralCustomComponentConverters<TProps> {}

/**
 * Definition of a vanilla JavaScript component.
 */
export interface HtmlComponent<TProps> {
  /**
   * Renders a component into the provided element using the given props and context.
   */
  component: ForeignComponent<TProps>;
  /**
   * The type of the HTML component.
   */
  type: "html";
}

/**
 * Generic definition of a framework-independent component.
 */
export interface ForeignComponent<TProps> {
  /**
   * Called when the component is mounted.
   * @param element The container hosting the element.
   * @param props The props to transport.
   * @param ctx The associated context.
   * @param locals The local state of this component instance.
   */
  mount(element: HTMLElement, props: TProps, ctx: ComponentContext, locals: Record<string, any>): void;
  /**
   * Called when the component should be updated.
   * @param element The container hosting the element.
   * @param props The props to transport.
   * @param ctx The associated context.
   * @param locals The local state of this component instance.
   */
  update?(element: HTMLElement, props: TProps, ctx: ComponentContext, locals: Record<string, any>): void;
  /**
   * Called when a component is unmounted.
   * @param element The container that was hosting the element.
   * @param locals The local state of this component instance.
   */
  unmount?(element: HTMLElement, locals: Record<string, any>): void;
}

/**
 * The props that every registered component obtains.
 */
export interface BaseComponentProps {
  /**
   * The currently used pilet API.
   */
  piral: PiletApi;
}

export interface BreadCrumbMeta {
  /**
   * A function that provides the value that should be used in the breadcrumb.
   */
  breadcrumb?: BreadcrumbResolver;
}

export interface PermissionMeta {
  permissions?: {
    [key: string]: Array<string>;
  };
}

/**
 * Custom extension slots outside of piral-core.
 */
export interface PiralCustomExtensionSlotMap {}

export interface HubItem extends TileProps {
  /**
   * Group name of the item
   */
  groupName: string;
}

export type ValueOrOnDemand = string | number | (() => Promise<string | number>);

/**
 * The base type for pilet component registration in the global state context.
 */
export interface BaseRegistration {
  /**
   * The pilet registering the component.
   */
  pilet: string;
}

export type WrappedComponent<TProps> = React.ComponentType<React.PropsWithChildren<Without<TProps, keyof BaseComponentProps>>>;

export type NotificationBody = string | React.ReactNode | React.Component;

export interface NotificationOptions {
  /**
   * Type of the notification
   */
  type?: NotificationType;
  /**
   * Set to false to disable auto-closing of the notification, or specify a custom timeout in milliseconds
   */
  autoClose?: number | false;
}

/**
 * The context to be transported into the generic components.
 */
export interface ComponentContext {
  /**
   * The router-independent navigation API.
   */
  navigation: NavigationApi___1;
  /**
   * The internal router object.
   * @deprecated Exposes internals that can change at any time.
   */
  router: any;
  /**
   * The public path of the application.
   */
  publicPath: string;
}

/**
 * A function that provides the value that should be used in the breadcrumb.
 */
export type BreadcrumbResolver = (routeParams: {
  [key: string]: string;
}) => Breadcrumb["label"];

export interface TileProps {
  /**
   * url path
   */
  path: string;
  /**
   * Tile label
   */
  label: string;
  /**
   * Tile sub label
   */
  subLabel?: string;
  /**
   * Icon location or an react element of svg icon.
   * if an react element of svg is passed, the correct style for two types ("small" and "large") will be applied accordingly
   * The styles which don't want to be affected based on the tile type can be passed as inline styles.
   */
  icon: string | React.ReactNode;
  /**
   * Tile is disabled or enabled, default is false
   */
  disabled?: boolean;
}

export type Without<T, K> = Pick<T, Exclude<keyof T, K>>;

export type NotificationType = "success" | "error" | "info" | "warning";

export interface NavigationApi___1 {
  /**
   * Pushes a new location onto the history stack.
   */
  push(target: string, state?: any): void;
  /**
   * Replaces the current location with another.
   */
  replace(target: string, state?: any): void;
  /**
   * Changes the current index in the history stack by a given delta.
   */
  go(n: number): void;
  /**
   * Prevents changes to the history stack from happening.
   * This is useful when you want to prevent the user navigating
   * away from the current page, for example when they have some
   * unsaved data on the current page.
   * @param blocker The function being called with a transition request.
   * @returns The disposable for stopping the block.
   */
  block(blocker: NavigationBlocker): Disposable;
  /**
   * Starts listening for location changes and calls the given
   * callback with an Update when it does.
   * @param listener The function being called when the route changes.
   * @returns The disposable for stopping the block.
   */
  listen(listener: NavigationListener): Disposable;
  /**
   * Gets the current navigation / application path.
   */
  path: string;
  /**
   * Gets the current navigation path incl. search and hash parts.
   */
  url: string;
  /**
   * The original router behind the navigation. Don't depend on this
   * as the implementation is router specific and may change over time.
   */
  router: any;
  /**
   * Gets the public path of the application.
   */
  publicPath: string;
}

/**
 * Breadcrumb
 */
export interface Breadcrumb {
  /**
   * Text to be displayed in breadcrumb
   */
  label: ValueOrOnDemand;
  /**
   * The link it leads to, must be absolute path
   */
  url: string;
  /**
   * Collection of route parameters
   */
  params: [];
}

export interface NavigationBlocker {
  (tx: NavigationTransition): void;
}

export interface NavigationListener {
  (update: NavigationUpdate): void;
}

export interface NavigationTransition extends NavigationUpdate {
  retry?(): void;
}

export interface NavigationUpdate {
  action: NavigationAction;
  location: NavigationLocation;
}

export type NavigationAction = "POP" | "PUSH" | "REPLACE";

export interface NavigationLocation {
  /**
   * The fully qualified URL incl. the origin and base path.
   */
  href: string;
  /**
   * The location.pathname property is a string that contains an initial "/"
   * followed by the remainder of the URL up to the ?.
   */
  pathname: string;
  /**
   * The location.search property is a string that contains an initial "?"
   * followed by the key=value pairs in the query string. If there are no
   * parameters, this value may be the empty string (i.e. '').
   */
  search: string;
  /**
   * The location.hash property is a string that contains an initial "#"
   * followed by fragment identifier of the URL. If there is no fragment
   * identifier, this value may be the empty string (i.e. '').
   */
  hash: string;
  /**
   * The location.state property is a user-supplied State object that is
   * associated with this location. This can be a useful place to store
   * any information you do not want to put in the URL, e.g. session-specific
   * data.
   */
  state: unknown;
  /**
   * The location.key property is a unique string associated with this location.
   * On the initial location, this will be the string default. On all subsequent
   * locations, this string will be a unique identifier.
   */
  key?: string;
}