import React, { useEffect, useState, ChangeEvent, KeyboardEvent } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { subscribe, useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next'
import clonedeep from 'lodash.clonedeep'

import Linkify from 'react-linkify'
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
import classNames from 'classnames'
import reactStringReplace from 'react-string-replace'

import Alert from '~components/Alert'
import Button from '~components/Button'
import CharLimitedFieldset from '~components/CharLimitedFieldset'
import DurationInput from '~components/DurationInput'
import GridRepCollection from '~components/GridRepCollection'
import GridRep from '~components/GridRep'
import Input from '~components/Input'
import RaidDropdown from '~components/RaidDropdown'
import Switch from '~components/Switch'
import Tooltip from '~components/Tooltip'
import TextFieldset from '~components/TextFieldset'
import Token from '~components/Token'

import api from '~utils/api'
import { accountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState'
import { formatTimeAgo } from '~utils/timeAgo'
import { youtube } from '~utils/youtube'

import CheckIcon from '~public/icons/Check.svg'
import CrossIcon from '~public/icons/Cross.svg'
import EditIcon from '~public/icons/Edit.svg'
import RemixIcon from '~public/icons/Remix.svg'

import type { DetailsObject } from 'types'

import './index.scss'

// Props
interface Props {
  party?: Party
  new: boolean
  editable: boolean
  updateCallback: (details: DetailsObject) => void
  deleteCallback: () => void
}

const PartyDetails = (props: Props) => {
  const { party, raids } = useSnapshot(appState)

  const { t } = useTranslation('common')
  const router = useRouter()
  const locale = router.locale || 'en'

  const youtubeUrlRegex =
    /(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g

  const nameInput = React.createRef<HTMLInputElement>()
  const descriptionInput = React.createRef<HTMLTextAreaElement>()

  const [open, setOpen] = useState(false)
  const [name, setName] = useState('')
  const [alertOpen, setAlertOpen] = useState(false)

  const [chargeAttack, setChargeAttack] = useState(true)
  const [fullAuto, setFullAuto] = useState(false)
  const [autoGuard, setAutoGuard] = useState(false)

  const [buttonCount, setButtonCount] = useState<number | undefined>(undefined)
  const [chainCount, setChainCount] = useState<number | undefined>(undefined)
  const [turnCount, setTurnCount] = useState<number | undefined>(undefined)
  const [clearTime, setClearTime] = useState(0)

  const [remixes, setRemixes] = useState<Party[]>([])

  const [raidSlug, setRaidSlug] = useState('')
  const [embeddedDescription, setEmbeddedDescription] =
    useState<React.ReactNode>()

  const readOnlyClasses = classNames({
    PartyDetails: true,
    ReadOnly: true,
    Visible: !open,
  })

  const editableClasses = classNames({
    PartyDetails: true,
    Editable: true,
    Visible: open,
  })

  const userClass = classNames({
    user: true,
    empty: !party.user,
  })

  const linkClass = classNames({
    wind: party && party.element == 1,
    fire: party && party.element == 2,
    water: party && party.element == 3,
    earth: party && party.element == 4,
    dark: party && party.element == 5,
    light: party && party.element == 6,
  })

  const [errors, setErrors] = useState<{ [key: string]: string }>({
    name: '',
    description: '',
  })

  useEffect(() => {
    if (props.party) {
      setName(props.party.name)
      setAutoGuard(props.party.auto_guard)
      setFullAuto(props.party.full_auto)
      setChargeAttack(props.party.charge_attack)
      setClearTime(props.party.clear_time)
      setRemixes(props.party.remixes)
      if (props.party.turn_count) setTurnCount(props.party.turn_count)
      if (props.party.button_count) setButtonCount(props.party.button_count)
      if (props.party.chain_count) setChainCount(props.party.chain_count)
    }
  }, [props.party])

  // Subscribe to router changes and reset state
  // if the new route is a new team
  useEffect(() => {
    router.events.on('routeChangeStart', (url, { shallow }) => {
      if (url === '/new' || url === '/') {
        const party = initialAppState.party

        setName(party.name ? party.name : '')
        setAutoGuard(party.autoGuard)
        setFullAuto(party.fullAuto)
        setChargeAttack(party.chargeAttack)
        setClearTime(party.clearTime)
        setRemixes(party.remixes)
        setTurnCount(party.turnCount)
        setButtonCount(party.buttonCount)
        setChainCount(party.chainCount)
      }
    })
  }, [])

  useEffect(() => {
    // Extract the video IDs from the description
    if (appState.party.description) {
      const videoIds = extractYoutubeVideoIds(appState.party.description)

      // Fetch the video titles for each ID
      const fetchPromises = videoIds.map(({ id }) => fetchYoutubeData(id))

      // Wait for all the video titles to be fetched
      Promise.all(fetchPromises).then((videoTitles) => {
        // Replace the video URLs in the description with LiteYoutubeEmbed elements
        const newDescription = reactStringReplace(
          appState.party.description,
          youtubeUrlRegex,
          (match, i) => (
            <LiteYouTubeEmbed
              key={`${match}-${i}`}
              id={match}
              title={videoTitles[i]}
              wrapperClass="YoutubeWrapper"
              playerClass="PlayerButton"
            />
          )
        )

        // Update the state with the new description
        setEmbeddedDescription(newDescription)
      })
    } else {
      setEmbeddedDescription('')
    }
  }, [appState.party.description])

  function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
    event.preventDefault()

    const { name, value } = event.target
    setName(value)

    let newErrors = errors
    setErrors(newErrors)
  }

  function handleTextAreaChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
    event.preventDefault()

    const { name, value } = event.target
    let newErrors = errors

    setErrors(newErrors)
  }

  function handleChargeAttackChanged(checked: boolean) {
    setChargeAttack(checked)
  }

  function handleFullAutoChanged(checked: boolean) {
    setFullAuto(checked)
  }

  function handleAutoGuardChanged(checked: boolean) {
    setAutoGuard(checked)
  }

  function handleClearTimeInput(value: number) {
    if (!isNaN(value)) setClearTime(value)
  }

  function handleTurnCountInput(event: React.ChangeEvent<HTMLInputElement>) {
    const value = parseInt(event.currentTarget.value)
    if (!isNaN(value)) setTurnCount(value)
  }

  function handleButtonCountInput(event: ChangeEvent<HTMLInputElement>) {
    const value = parseInt(event.currentTarget.value)
    if (!isNaN(value)) setButtonCount(value)
  }

  function handleChainCountInput(event: ChangeEvent<HTMLInputElement>) {
    const value = parseInt(event.currentTarget.value)
    if (!isNaN(value)) setChainCount(value)
  }

  function handleInputKeyDown(event: KeyboardEvent<HTMLInputElement>) {
    if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
      // Allow the key to be processed normally
      return
    }

    // Get the current value
    const input = event.currentTarget
    let value = event.currentTarget.value

    // Check if the key that was pressed is the backspace key
    if (event.key === 'Backspace') {
      // Remove the colon if the value is "12:"
      if (value.length === 4) {
        value = value.slice(0, -1)
      }

      // Allow the backspace key to be processed normally
      input.value = value
      return
    }

    // Check if the key that was pressed is the tab key
    if (event.key === 'Tab') {
      // Allow the tab key to be processed normally
      return
    }

    // Get the character that was entered and check if it is numeric
    const char = parseInt(event.key)
    const isNumber = !isNaN(char)

    // Check if the character should be accepted or rejected
    const numberValue = parseInt(`${value}${char}`)
    const minValue = parseInt(event.currentTarget.min)
    const maxValue = parseInt(event.currentTarget.max)

    if (!isNumber || numberValue < minValue || numberValue > maxValue) {
      // Reject the character if it isn't a number,
      // or if it exceeds the min and max values
      event.preventDefault()
    }
  }

  async function fetchYoutubeData(videoId: string) {
    return await youtube
      .getVideoById(videoId, { maxResults: 1 })
      .then((data) => data.items[0].snippet.localized.title)
  }

  function toggleDetails() {
    // Enabling this code will make live updates not work,
    // but I'm not sure why it's here, so we're not going to remove it.

    // if (name !== party.name) {
    //   const resetName = party.name ? party.name : ''
    //   setName(resetName)
    //   if (nameInput.current) nameInput.current.value = resetName
    // }
    setOpen(!open)
  }

  function receiveRaid(slug?: string) {
    if (slug) setRaidSlug(slug)
  }

  function switchValue(value: boolean) {
    if (value) return 'on'
    else return 'off'
  }

  function updateDetails(event: React.MouseEvent) {
    const descriptionValue = descriptionInput.current?.value
    const raid = raids.find((raid) => raid.slug === raidSlug)

    const details: DetailsObject = {
      fullAuto: fullAuto,
      autoGuard: autoGuard,
      chargeAttack: chargeAttack,
      clearTime: clearTime,
      buttonCount: buttonCount,
      turnCount: turnCount,
      chainCount: chainCount,
      name: name,
      description: descriptionValue,
      raid: raid,
    }

    props.updateCallback(details)
    toggleDetails()
  }

  function handleClick() {
    setAlertOpen(!alertOpen)
  }

  function deleteParty() {
    props.deleteCallback()
  }

  // Methods: Navigation
  function goTo(shortcode?: string) {
    if (shortcode) router.push(`/p/${shortcode}`)
  }

  // Methods: Favorites
  function toggleFavorite(teamId: string, favorited: boolean) {
    if (favorited) unsaveFavorite(teamId)
    else saveFavorite(teamId)
  }

  function saveFavorite(teamId: string) {
    api.saveTeam({ id: teamId }).then((response) => {
      if (response.status == 201) {
        const index = remixes.findIndex((p) => p.id === teamId)
        const party = remixes[index]

        party.favorited = true

        let clonedParties = clonedeep(remixes)
        clonedParties[index] = party

        setRemixes(clonedParties)
      }
    })
  }

  function unsaveFavorite(teamId: string) {
    api.unsaveTeam({ id: teamId }).then((response) => {
      if (response.status == 200) {
        const index = remixes.findIndex((p) => p.id === teamId)
        const party = remixes[index]

        party.favorited = false

        let clonedParties = clonedeep(remixes)
        clonedParties[index] = party

        setRemixes(clonedParties)
      }
    })
  }

  function extractYoutubeVideoIds(text: string) {
    // Initialize an array to store the video IDs
    const videoIds = []

    // Use the regular expression to find all the Youtube URLs in the text
    let match
    while ((match = youtubeUrlRegex.exec(text)) !== null) {
      // Extract the video ID from the URL
      const videoId = match[1]

      // Add the video ID to the array, along with the character position of the URL
      videoIds.push({
        id: videoId,
        url: match[0],
        position: match.index,
      })
    }

    // Return the array of video IDs
    return videoIds
  }

  const userImage = (picture?: string, element?: string) => {
    if (picture && element)
      return (
        <img
          alt={picture}
          className={`profile ${element}`}
          srcSet={`/profile/${picture}.png,
                            /profile/${picture}@2x.png 2x`}
          src={`/profile/${picture}.png`}
        />
      )
    else
      return (
        <img
          alt={t('no_user')}
          className={`profile anonymous`}
          srcSet={`/profile/npc.png,
                            /profile/npc@2x.png 2x`}
          src={`/profile/npc.png`}
        />
      )
  }

  const userBlock = (username?: string, picture?: string, element?: string) => {
    return (
      <div className={userClass}>
        {userImage(picture, element)}
        {username ? username : t('no_user')}
      </div>
    )
  }

  const renderUserBlock = () => {
    let username, picture, element
    if (accountState.account.authorized && props.new) {
      username = accountState.account.user?.username
      picture = accountState.account.user?.avatar.picture
      element = accountState.account.user?.avatar.element
    } else if (party.user && !props.new) {
      username = party.user.username
      picture = party.user.avatar.picture
      element = party.user.avatar.element
    }

    if (username && picture && element) {
      return linkedUserBlock(username, picture, element)
    } else if (!props.new) {
      return userBlock()
    }
  }

  const linkedUserBlock = (
    username?: string,
    picture?: string,
    element?: string
  ) => {
    return (
      <div>
        <Link href={`/${username}`} passHref>
          <a className={linkClass}>{userBlock(username, picture, element)}</a>
        </Link>
      </div>
    )
  }

  const linkedRaidBlock = (raid: Raid) => {
    return (
      <div>
        <Link href={`/teams?raid=${raid.slug}`} passHref>
          <a className={`Raid ${linkClass}`}>{raid.name[locale]}</a>
        </Link>
      </div>
    )
  }

  function renderRemixes() {
    return remixes.map((party, i) => {
      return (
        <GridRep
          id={party.id}
          shortcode={party.shortcode}
          name={party.name}
          createdAt={new Date(party.created_at)}
          raid={party.raid}
          grid={party.weapons}
          user={party.user}
          favorited={party.favorited}
          fullAuto={party.full_auto}
          key={`party-${i}`}
          displayUser={true}
          onClick={goTo}
          onSave={toggleFavorite}
        />
      )
    })
  }

  const deleteAlert = () => {
    if (party.editable) {
      return (
        <Alert
          open={alertOpen}
          primaryAction={deleteParty}
          primaryActionText={t('modals.delete_team.buttons.confirm')}
          cancelAction={() => setAlertOpen(false)}
          cancelActionText={t('modals.delete_team.buttons.cancel')}
          message={t('modals.delete_team.description')}
        />
      )
    }
  }

  const editable = () => {
    return (
      <section className={editableClasses}>
        <CharLimitedFieldset
          fieldName="name"
          placeholder="Name your team"
          value={props.party?.name}
          limit={50}
          onChange={handleInputChange}
          error={errors.name}
          ref={nameInput}
        />
        <RaidDropdown
          showAllRaidsOption={false}
          currentRaid={props.party?.raid ? props.party?.raid.slug : undefined}
          onChange={receiveRaid}
        />
        <ul className="SwitchToggleGroup DetailToggleGroup">
          <li className="Ougi ToggleSection">
            <label htmlFor="ougi">
              <span>{t('party.details.labels.charge_attack')}</span>
              <div>
                <Switch
                  name="charge_attack"
                  onCheckedChange={handleChargeAttackChanged}
                  value={switchValue(chargeAttack)}
                  checked={chargeAttack}
                />
              </div>
            </label>
          </li>
          <li className="FullAuto ToggleSection">
            <label htmlFor="full_auto">
              <span>{t('party.details.labels.full_auto')}</span>
              <div>
                <Switch
                  onCheckedChange={handleFullAutoChanged}
                  name="full_auto"
                  value={switchValue(fullAuto)}
                  checked={fullAuto}
                />
              </div>
            </label>
          </li>
          <li className="AutoGuard ToggleSection">
            <label htmlFor="auto_guard">
              <span>{t('party.details.labels.auto_guard')}</span>
              <div>
                <Switch
                  onCheckedChange={handleAutoGuardChanged}
                  name="auto_guard"
                  value={switchValue(autoGuard)}
                  disabled={!fullAuto}
                  checked={autoGuard}
                />
              </div>
            </label>
          </li>
        </ul>
        <ul className="InputToggleGroup DetailToggleGroup">
          <li className="InputSection">
            <label htmlFor="auto_guard">
              <span>{t('party.details.labels.button_chain')}</span>
              <div className="Input Bound">
                <Input
                  name="buttons"
                  type="number"
                  placeholder="0"
                  value={`${buttonCount}`}
                  min="0"
                  max="99"
                  onChange={handleButtonCountInput}
                  onKeyDown={handleInputKeyDown}
                />
                <span>b</span>
                <Input
                  name="chains"
                  type="number"
                  placeholder="0"
                  min="0"
                  max="99"
                  value={`${chainCount}`}
                  onChange={handleChainCountInput}
                  onKeyDown={handleInputKeyDown}
                />
                <span>c</span>
              </div>
            </label>
          </li>
          <li className="InputSection">
            <label htmlFor="auto_guard">
              <span>{t('party.details.labels.turn_count')}</span>
              <Input
                name="turn_count"
                className="AlignRight Bound"
                type="number"
                step="1"
                min="1"
                max="999"
                placeholder="0"
                value={`${turnCount}`}
                onChange={handleTurnCountInput}
                onKeyDown={handleInputKeyDown}
              />
            </label>
          </li>
          <li className="InputSection">
            <label htmlFor="auto_guard">
              <span>{t('party.details.labels.clear_time')}</span>
              <div>
                <DurationInput
                  name="clear_time"
                  className="Bound"
                  placeholder="00:00"
                  value={clearTime}
                  onValueChange={(value: number) => handleClearTimeInput(value)}
                />
              </div>
            </label>
          </li>
        </ul>
        <TextFieldset
          fieldName="name"
          placeholder={
            'Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 3 first\nGood luck with RNG!'
          }
          value={props.party?.description}
          onChange={handleTextAreaChange}
          error={errors.description}
          ref={descriptionInput}
        />

        <div className="bottom">
          <div className="left">
            {router.pathname !== '/new' ? (
              <Button
                leftAccessoryIcon={<CrossIcon />}
                className="Blended medium destructive"
                onClick={handleClick}
                text={t('buttons.delete')}
              />
            ) : (
              ''
            )}
          </div>
          <div className="right">
            <Button text={t('buttons.cancel')} onClick={toggleDetails} />
            <Button
              leftAccessoryIcon={<CheckIcon className="Check" />}
              text={t('buttons.save_info')}
              onClick={updateDetails}
            />
          </div>
        </div>
      </section>
    )
  }

  const clearTimeString = () => {
    const minutes = Math.floor(clearTime / 60)
    const seconds = clearTime - minutes * 60

    if (minutes > 0)
      return `${minutes}${t('party.details.suffix.minutes')} ${seconds}${t(
        'party.details.suffix.seconds'
      )}`
    else return `${seconds}${t('party.details.suffix.seconds')}`
  }

  const buttonChainToken = () => {
    if (buttonCount || chainCount) {
      let string = ''

      if (buttonCount && buttonCount > 0) {
        string += `${buttonCount}b`
      }

      if (!buttonCount && chainCount && chainCount > 0) {
        string += `0${t('party.details.suffix.buttons')}${chainCount}${t(
          'party.details.suffix.chains'
        )}`
      } else if (buttonCount && chainCount && chainCount > 0) {
        string += `${chainCount}${t('party.details.suffix.chains')}`
      } else if (buttonCount && !chainCount) {
        string += `0${t('party.details.suffix.chains')}`
      }

      return <Token>{string}</Token>
    }
  }

  const readOnly = () => {
    return (
      <section className={readOnlyClasses}>
        <section className="Details">
          <Token
            className={classNames({
              ChargeAttack: true,
              On: chargeAttack,
              Off: !chargeAttack,
            })}
          >
            {`${t('party.details.labels.charge_attack')} ${
              chargeAttack ? 'On' : 'Off'
            }`}
          </Token>

          <Token
            className={classNames({
              FullAuto: true,
              On: fullAuto,
              Off: !fullAuto,
            })}
          >
            {`${t('party.details.labels.full_auto')} ${
              fullAuto ? 'On' : 'Off'
            }`}
          </Token>

          <Token
            className={classNames({
              AutoGuard: true,
              On: autoGuard,
              Off: !autoGuard,
            })}
          >
            {`${t('party.details.labels.auto_guard')} ${
              fullAuto ? 'On' : 'Off'
            }`}
          </Token>

          {turnCount ? (
            <Token>
              {t('party.details.turns.with_count', {
                count: turnCount,
              })}
            </Token>
          ) : (
            ''
          )}
          {clearTime > 0 ? <Token>{clearTimeString()}</Token> : ''}
          {buttonChainToken()}
        </section>
        <Linkify>{embeddedDescription}</Linkify>
      </section>
    )
  }

  const remixSection = () => {
    return (
      <section className="Remixes">
        <h3>{t('remixes')}</h3>
        {<GridRepCollection>{renderRemixes()}</GridRepCollection>}
      </section>
    )
  }

  return (
    <>
      <section className="DetailsWrapper">
        <div className="PartyInfo">
          <div className="Left">
            <div className="Header">
              <h1 className={name ? '' : 'empty'}>
                {name ? name : t('no_title')}
              </h1>
              {party.remix && party.sourceParty ? (
                <Tooltip content={t('tooltips.source')}>
                  <Button
                    className="IconButton Blended"
                    leftAccessoryIcon={<RemixIcon />}
                    text={t('tokens.remix')}
                    onClick={() => goTo(party.sourceParty?.shortcode)}
                  />
                </Tooltip>
              ) : (
                ''
              )}
            </div>
            <div className="attribution">
              {renderUserBlock()}
              {party.raid ? linkedRaidBlock(party.raid) : ''}
              {party.created_at != '' ? (
                <time
                  className="last-updated"
                  dateTime={new Date(party.created_at).toString()}
                >
                  {formatTimeAgo(new Date(party.created_at), locale)}
                </time>
              ) : (
                ''
              )}
            </div>
          </div>
          {party.editable ? (
            <div className="Right">
              <Button
                leftAccessoryIcon={<EditIcon />}
                text={t('buttons.show_info')}
                onClick={toggleDetails}
              />
            </div>
          ) : (
            ''
          )}
        </div>
        {readOnly()}
        {editable()}

        {deleteAlert()}
      </section>
      {remixes && remixes.length > 0 ? remixSection() : ''}
    </>
  )
}

export default PartyDetails
