import {
  randomId,
  getSearchParamsByKeys,
  getUrlSearchParams,
} from '@seamly/web-ui'
import { FC } from 'preact/compat'
import { useCallback, useEffect, useState } from 'preact/hooks'
import { getStateObj } from '../states'
import StyleGuideLinks from './links'
import StyleGuideView from './view'

type HProps = {
  tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
  id: string
}
const H: FC<HProps> = ({ tag, id, children }) => {
  const Component = tag

  return <Component id={id}>{children}</Component>
}

const StyleGuideApp = ({
  config,
  styleGuideConfig,
  translations,
  headingLevel = 2,
  participants,
}) => {
  const [staticState, setStaticState] = useState<Record<string, any> | null>(
    null,
  )
  const [selectedStateDescription, setSelectedStateDescription] = useState('')
  const [showStyleGuide, setShowStyleGuide] = useState(true)
  const [headingId] = useState(randomId())
  const { stateUpdateCallback, customMessageEventBodies } = styleGuideConfig
  const [mainState] = useState(() => {
    const mainStateObj = getStateObj(
      styleGuideConfig.showLayoutModes || ['inline', 'window'],
      customMessageEventBodies,
    )

    return Object.keys(mainStateObj).reduce(
      (acc, key) => ({
        ...acc,
        [key]: {
          ...mainStateObj[key],
          descriptionId: randomId(),
        },
      }),
      {},
    )
  })

  const updateState = useCallback(
    (state) => {
      if (stateUpdateCallback) {
        setStaticState(stateUpdateCallback(state))
      } else {
        setStaticState(state)
      }
    },
    [setStaticState, stateUpdateCallback],
  )

  const getState = useCallback(
    (layoutMode, feature) => {
      const keys = getSearchParamsByKeys('apiKey', 'locale')
      const combinedParams = new URLSearchParams({
        ...keys,
        layoutMode,
        feature,
      })
      window.history.replaceState(null, '', `?${combinedParams}`)
      const { headingText } = mainState[feature]
      setSelectedStateDescription(headingText)
      const bareState = mainState[feature][layoutMode]
      const { overrideMessages, showSuggestions } = bareState.config
      const agent = participants.find(
        (participant) => participant.id === 'agent',
      )

      const updatedState = {
        ...bareState,
        config: {
          ...bareState.config,
          ...config,
          showDisclaimer:
            bareState.config.showDisclaimer || config.showDisclaimer,
          context: {
            ...config.context,
            ...bareState.config?.context,
          },
          layoutMode: bareState.config.layoutMode,
          ...(overrideMessages ? { messages: overrideMessages } : {}),
          showSuggestions,
        },
        headerTitles: {
          ...bareState.headerTitles,
          subTitle: agent?.name || bareState.headerTitles.subTitle,
        },
      }
      return updatedState
    },
    [mainState, participants, config],
  )

  useEffect(() => {
    // As the modal focus trap disables clicking on elements outside the modal container,
    // we need to hide the modal manually on clicking the overlay as the internal
    // close action is not functional.
    if (showStyleGuide) {
      const overlay = document.querySelector('[aria-modal="true"]')
      if (overlay) {
        overlay.addEventListener('click', () => {
          setStaticState((s) => {
            if (!s) return s
            return {
              ...s,
              visibility: { ...s.visibility, visible: 'minimized' },
            }
          })
        })
      }
    }
  }, [staticState, setStaticState, showStyleGuide])

  const loadFromUrl = useCallback(() => {
    const params = getUrlSearchParams()

    const { layoutMode, feature } = {
      layoutMode: 'inline',
      feature: 'base',
      ...params,
    }

    updateState(getState(layoutMode, feature))
    window.document.title = `Style`
  }, [getState, updateState])

  useEffect(() => {
    loadFromUrl()
  }, [loadFromUrl])

  useEffect(() => {
    window.onpopstate = () => {
      loadFromUrl()
    }
  }, [loadFromUrl])

  const setSelectedState = ({ layoutMode, feature }) => {
    const renderState = getState(layoutMode, feature)
    updateState(renderState)
    if (renderState.styleGuideReset) {
      // This is required for changes where a previously rendered component may cause the
      // state not to resolve properly. For example: File uploads.
      // So we render the base state briefly to reset everything.
      setShowStyleGuide(false)
      setTimeout(() => {
        setShowStyleGuide(true)
      })
    }
  }

  return (
    <>
      <section className="chat-inline" aria-labelledby={headingId}>
        <H tag={`h${headingLevel.toString()}` as HProps['tag']} id={headingId}>
          <span className="heading__context">Current selected state:</span>
          {`${selectedStateDescription}`}
        </H>
        <div>
          {showStyleGuide && (
            <StyleGuideView
              customComponents={styleGuideConfig.customComponents}
              translations={translations}
              state={staticState}
              participants={participants}
            />
          )}
        </div>
      </section>
      <aside className="page-aside markdown">
        <nav className="page-controls">
          <StyleGuideLinks
            headingLevel={headingLevel}
            setSelectedState={setSelectedState}
            states={mainState}
          />
        </nav>
      </aside>
    </>
  )
}

export default StyleGuideApp
