import { JSX as LocalJSX } from '@ionic/core';
import React, { useContext } from 'react';

import { NavContext } from '../../contexts/NavContext';
import { RouteInfo } from '../../models';
import { IonicReactProps } from '../IonicReactProps';
import { IonTabBarInner } from '../inner-proxies';
import { createForwardRef } from '../utils';

import { IonTabButton } from './IonTabButton';

type IonTabBarProps = LocalJSX.IonTabBar & IonicReactProps & {
  onIonTabsDidChange?: (event: CustomEvent<{ tab: string; }>) => void;
  onIonTabsWillChange?: (event: CustomEvent<{ tab: string; }>) => void;
  slot?: 'bottom' | 'top';
  style?: { [key: string]: string; };
};

interface InternalProps extends IonTabBarProps {
  forwardedRef?: React.RefObject<HTMLIonIconElement>;
  onSetCurrentTab: (tab: string, routeInfo: RouteInfo) => void;
  routeInfo: RouteInfo;
}

interface TabUrls {
  originalHref: string;
  currentHref: string;
  originalRouteOptions?: unknown;
  currentRouteOptions?: unknown;
}

interface IonTabBarState {
  activeTab?: string;
  tabs: { [key: string]: TabUrls; };
}

class IonTabBarUnwrapped extends React.PureComponent<InternalProps, IonTabBarState> {
  context!: React.ContextType<typeof NavContext>;

  constructor(props: InternalProps) {
    super(props);
    const tabs: { [key: string]: TabUrls; } = {};
    React.Children.forEach((props as any).children, (child: any) => {
      if (child != null && typeof child === 'object' && child.props && child.type === IonTabButton) {
        tabs[child.props.tab] = {
          originalHref: child.props.href,
          currentHref: child.props.href,
          originalRouteOptions: child.props.href === props.routeInfo?.pathname ? props.routeInfo?.routeOptions : undefined,
          currentRouteOptions: child.props.href === props.routeInfo?.pathname ? props.routeInfo?.routeOptions : undefined,
        };
      }
    });

    this.state = {
      tabs
    };

    this.onTabButtonClick = this.onTabButtonClick.bind(this);
    this.renderTabButton = this.renderTabButton.bind(this);
    this.setActiveTabOnContext = this.setActiveTabOnContext.bind(this);
    this.selectTab = this.selectTab.bind(this);
  }

  componentDidMount() {
    const tabs = this.state.tabs;
    const tabKeys = Object.keys(tabs);
    const activeTab = tabKeys
      .find(key => {
        const href = tabs[key].originalHref;
        return this.props.routeInfo!.pathname.startsWith(href);
      });

    if (activeTab) {
      this.setState({
        activeTab
      });
    }
  }

  componentDidUpdate() {
    if (this.state.activeTab) {
      this.setActiveTabOnContext(this.state.activeTab);
    }
  }

  setActiveTabOnContext = (_tab: string) => { };

  selectTab(tab: string) {
    const tabUrl = this.state.tabs[tab];
    if (tabUrl) {
      this.onTabButtonClick(new CustomEvent('ionTabButtonClick', {
        detail: {
          href: tabUrl.currentHref,
          tab,
          selected: tab === this.state.activeTab,
          routeOptions: undefined
        }
      }));
      return true;
    }
    return false;
  }

  static getDerivedStateFromProps(props: InternalProps, state: IonTabBarState) {
    const tabs = { ...state.tabs };
    const tabKeys = Object.keys(state.tabs);
    const activeTab = tabKeys
      .find(key => {
        const href = state.tabs[key].originalHref;
        return props.routeInfo!.pathname.startsWith(href);
      });

    // Check to see if the tab button href has changed, and if so, update it in the tabs state
    React.Children.forEach((props as any).children, (child: any) => {
      if (child != null && typeof child === 'object' && child.props && child.type === IonTabButton) {
        const tab = tabs[child.props.tab];
        if (!tab || (tab.originalHref !== child.props.href)) {
          tabs[child.props.tab] = {
            originalHref: child.props.href,
            currentHref: child.props.href,
            originalRouteOptions: child.props.routeOptions,
            currentRouteOptions: child.props.routeOptions
          };
        }
      }
    });

    const { activeTab: prevActiveTab } = state;
    if (activeTab && prevActiveTab) {
      const prevHref = state.tabs[prevActiveTab].currentHref;
      const prevRouteOptions = state.tabs[prevActiveTab].currentRouteOptions;
      if (activeTab !== prevActiveTab || (prevHref !== props.routeInfo?.pathname || prevRouteOptions !== props.routeInfo?.routeOptions)) {
        tabs[activeTab] = {
          originalHref: tabs[activeTab].originalHref,
          currentHref: props.routeInfo!.pathname + (props.routeInfo!.search || ''),
          originalRouteOptions: tabs[activeTab].originalRouteOptions,
          currentRouteOptions: props.routeInfo?.routeOptions
        };
        if (props.routeInfo.routeAction === 'pop' && (activeTab !== prevActiveTab)) {
          // If navigating back and the tabs change, set the prev tab back to its original href
          tabs[prevActiveTab] = {
            originalHref: tabs[prevActiveTab].originalHref,
            currentHref: tabs[prevActiveTab].originalHref,
            originalRouteOptions: tabs[prevActiveTab].originalRouteOptions,
            currentRouteOptions: tabs[prevActiveTab].currentRouteOptions
          };
        }
      }
    }

    activeTab && props.onSetCurrentTab(activeTab, props.routeInfo);

    return {
      activeTab,
      tabs
    };
  }

  private onTabButtonClick(e: CustomEvent<{ href: string, selected: boolean, tab: string; routeOptions: unknown; }>) {
    const tappedTab = this.state.tabs[e.detail.tab];
    const originalHref = tappedTab.originalHref;
    const currentHref = e.detail.href;
    const { activeTab: prevActiveTab } = this.state;
    // this.props.onSetCurrentTab(e.detail.tab, this.props.routeInfo);
    // Clicking the current tab will bring you back to the original href
    if (prevActiveTab === e.detail.tab) {
      if (originalHref !== currentHref) {
        this.context.resetTab(e.detail.tab, originalHref, tappedTab.originalRouteOptions);
      }
    } else {
      if (this.props.onIonTabsWillChange) {
        this.props.onIonTabsWillChange(new CustomEvent('ionTabWillChange', { detail: { tab: e.detail.tab } }));
      }
      if (this.props.onIonTabsDidChange) {
        this.props.onIonTabsDidChange(new CustomEvent('ionTabDidChange', { detail: { tab: e.detail.tab } }));
      }
      this.setActiveTabOnContext(e.detail.tab);
      this.context.changeTab(e.detail.tab, currentHref, e.detail.routeOptions);
    }
  }

  private renderTabButton(activeTab: string | null | undefined) {

    return (child: (React.ReactElement<LocalJSX.IonTabButton & { onClick: (e: any) => void; routeOptions?: unknown; }>) | null | undefined) => {
      if (child != null && child.props && child.type === IonTabButton) {
        const href = (child.props.tab === activeTab) ? this.props.routeInfo?.pathname : (this.state.tabs[child.props.tab!].currentHref);
        const routeOptions = (child.props.tab === activeTab) ? this.props.routeInfo?.routeOptions : (this.state.tabs[child.props.tab!].currentRouteOptions);

        return React.cloneElement(child, {
          href,
          routeOptions,
          onClick: this.onTabButtonClick
        });
      }
      return null;
    };
  }

  render() {
    const { activeTab } = this.state;
    return (
      <IonTabBarInner {...this.props} selectedTab={activeTab}>
        {React.Children.map(this.props.children as any, this.renderTabButton(activeTab))}
      </IonTabBarInner>
    );
  }

  static get contextType() {
    return NavContext;
  }
}

const IonTabBarContainer: React.FC<InternalProps> = React.memo<InternalProps>(({ forwardedRef, ...props }) => {
  const context = useContext(NavContext);
  return (
    <IonTabBarUnwrapped
      ref={forwardedRef}
      {...props as any}
      routeInfo={props.routeInfo || context.routeInfo || { pathname: window.location.pathname }}
      onSetCurrentTab={context.setCurrentTab}
    >
      {props.children}
    </IonTabBarUnwrapped>
  );
});

export const IonTabBar = createForwardRef<IonTabBarProps, HTMLIonTabBarElement>(IonTabBarContainer, 'IonTabBar');
