import { startChatQuery } from '@/queries/startChatQuery'
import { BotContext, OutgoingLog } from '@/types'
import { setBotContainerHeight } from '@/utils/botContainerHeightSignal'
import { setBotContainer } from '@/utils/botContainerSignal'
import { CorsError } from '@/utils/CorsError'
import { injectFont } from '@/utils/injectFont'
import { setIsMobile } from '@/utils/isMobileSignal'
import { persist } from '@/utils/persist'
import { setCssVariablesValue } from '@/utils/setCssVariablesValue'
import {
  getExistingResultIdFromStorage,
  getInitialChatReplyFromStorage,
  setInitialChatReplyInStorage,
  setResultInStorage,
  wipeExistingChatStateInStorage,
} from '@/utils/storage'
import { toaster } from '@/utils/toaster'
import { Toast, Toaster } from '@ark-ui/solid'
import { isDefined, isNotDefined, isNotEmpty } from '@indite.io/lib'
import {
  Font,
  InputBlock,
  StartChatResponse,
  StartFrom,
} from '@indite.io/schemas'
import { defaultSettings } from '@indite.io/schemas/features/bot/settings/constants'
import {
  defaultFontFamily,
  defaultFontType,
  defaultProgressBarPosition,
} from '@indite.io/schemas/features/bot/theme/constants'
import { clsx } from 'clsx'
import { HTTPError } from 'ky'
import { createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { Portal } from 'solid-js/web'
import immutableCss from '../assets/immutable.css'
import { ConversationContainer } from './ConversationContainer'
import { ErrorMessage } from './ErrorMessage'
import { CloseIcon } from './icons/CloseIcon'
import { LiteBadge } from './LiteBadge'
import { ProgressBar } from './ProgressBar'

export type BotProps = {
  source?: string
  botName?: string | undefined
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  bot: string | any
  isPreview?: boolean
  resultId?: string
  prefilledVariables?: Record<string, unknown>
  apiHost?: string
  font?: Font
  progressBarRef?: HTMLDivElement
  startFrom?: StartFrom
  sessionId?: string
  onNewInputBlock?: (inputBlock: InputBlock) => void
  onAnswer?: (answer: { message: string; blockId: string }) => void
  onInit?: () => void
  onEnd?: () => void
  onNewLogs?: (logs: OutgoingLog[]) => void
  onChatStatePersisted?: (isEnabled: boolean) => void
}
// utils/sharedValue.ts
type SharedValue<T> = {
  value: T;
  getValue: () => T;
  updateValue: (newValue: T) => void;
};
type RichTextChild = {
  text: string;
};

type RichTextElement = {
  type: 'p'; // Assuming only paragraphs are used for now
  children: RichTextChild[];
};

type MessageContent = {
  type: 'richText';
  richText: RichTextElement[];
};

type Message = {
  id: string;
  type: 'text';
  text: string; // Add the required 'text' property
  attachedFileUrls?: string[]; // Optional property to match the expected structure
  content: MessageContent;
};
type Bot = {
  id: string;
  theme: Record<string, unknown>; // Adjust as necessary based on your theme structure
  settings: Record<string, unknown>; // Adjust as necessary based on your settings structure
};
export type ConversationData = {
  sessionId: string;
  bot: Bot;
  messages: Message[];
  input: InputBlock;
};

export function createSharedValue<T>(initialValue: T): SharedValue<T> {
  let currentValue = initialValue;

  return {
    get value() {
      return currentValue;
    },
    getValue: () => currentValue,
    updateValue: (newValue: T) => {
      currentValue = newValue;
    },
  };
}

// Initialize a shared instance
export const sharedStringValue = createSharedValue<string | ConversationData | null>(null);

export const Bot = (props: BotProps & { class?: string, isTemplateModalView?: boolean, source?: string }) => {
  const [initialChatReply, setInitialChatReply] = createSignal<
    StartChatResponse | undefined
  >()
  const [customCss, setCustomCss] = createSignal('')
  const [isInitialized, setIsInitialized] = createSignal(false)
  const [error, setError] = createSignal<Error | undefined>()

  const initializeBot = async () => {
    if (props.font) injectFont(props.font)
    setIsInitialized(true)
    const urlParams = new URLSearchParams(location.search)
    props.onInit?.()
    const prefilledVariables: { [key: string]: string } = {}
    urlParams.forEach((value, key) => {
      prefilledVariables[key] = value
    })
    const botIdFromProps =
      typeof props.bot === 'string' ? props.bot : undefined
    const isPreview =
      typeof props.bot !== 'string' || (props.isPreview ?? false)
    const resultIdInStorage = getExistingResultIdFromStorage(botIdFromProps)
    console.log('props', props);
    
    const { data, error } = await startChatQuery({
      stripeRedirectStatus: urlParams.get('redirect_status') ?? undefined,
      bot: props.bot,
      apiHost: props.apiHost,
      source: props.source,
      isPreview,
      resultId: isNotEmpty(props.resultId) ? props.resultId : resultIdInStorage,
      prefilledVariables: {
        ...prefilledVariables,
        ...props.prefilledVariables,
      },
      startFrom: props.startFrom,
      sessionId: props.sessionId,
    })

    if (data && typeof data === 'object' && 'messages' in data && 'bot' in data && 'sessionId' in data && 'input' in data) {
      sharedStringValue.updateValue(data as ConversationData);
    } else {
      console.error('Invalid data format:', data);
    }
    if (error instanceof HTTPError) {
      if (isPreview) {
        return setError(
          new Error(`An error occurred while loading the bot.`, {
            cause: {
              status: error.response.status,
              body: await error.response.json(),
            },
          })
        )
      }
      if (error.response.status === 400 || error.response.status === 403)
        return setError(new Error('This bot is now closed.'))
      if (error.response.status === 404)
        return setError(new Error("The bot you're looking for doesn't exist."))
      return setError(
        new Error(
          `Error! Couldn't initiate the chat. (${error.response.statusText})`
        )
      )
    }

    if (error instanceof CorsError) {
      return setError(new Error(error.message))
    }

    if (!data) {
      if (error) {
        console.error(error)
        if (isPreview) {
          return setError(
            new Error(`Error! Could not reach server. Check your connection.`, {
              cause: error,
            })
          )
        }
      }
      return setError(
        new Error('Error! Could not reach server. Check your connection.')
      )
    }

    if (
      data.resultId &&
      botIdFromProps &&
      (data.bot.settings.general?.rememberUser?.isEnabled ??
        defaultSettings.general.rememberUser.isEnabled)
    ) {
      if (resultIdInStorage && resultIdInStorage !== data.resultId)
        wipeExistingChatStateInStorage(data.bot.id)
      const storage =
        data.bot.settings.general?.rememberUser?.storage ??
        defaultSettings.general.rememberUser.storage
      setResultInStorage(storage)(botIdFromProps, data.resultId)
      const initialChatInStorage = getInitialChatReplyFromStorage(
        data.bot.id
      )
      if (
        initialChatInStorage &&
        initialChatInStorage.bot.publishedAt &&
        data.bot.publishedAt
      ) {
        if (
          new Date(initialChatInStorage.bot.publishedAt).getTime() ===
          new Date(data.bot.publishedAt).getTime()
        ) {
          setInitialChatReply(initialChatInStorage)
        } else {
          // Restart chat by resetting remembered state
          wipeExistingChatStateInStorage(data.bot.id)
          setInitialChatReply(data)
          setInitialChatReplyInStorage(data, {
            botId: data.bot.id,
            storage,
          })
        }
      } else {
        setInitialChatReply(data)
        setInitialChatReplyInStorage(data, {
          botId: data.bot.id,
          storage,
        })
      }
      props.onChatStatePersisted?.(true)
    } else {
      wipeExistingChatStateInStorage(data.bot.id)
      setInitialChatReply(data)
      if (data.input?.id && props.onNewInputBlock)
        props.onNewInputBlock(data.input)
      if (data.logs) props.onNewLogs?.(data.logs)
      props.onChatStatePersisted?.(false)
    }

    setCustomCss(data.bot.theme.customCss ?? '')
  }

  createEffect(() => {
    if (isNotDefined(props.bot) || isInitialized()) return
    initializeBot().then()
  })

  createEffect(() => {
    if (isNotDefined(props.bot) || typeof props.bot === 'string') return
    setCustomCss(props.bot.theme.customCss ?? '')
    if (
      props.bot.theme.general?.progressBar?.isEnabled &&
      initialChatReply() &&
      !initialChatReply()?.bot.theme.general?.progressBar?.isEnabled
    ) {
      setIsInitialized(false)
      initializeBot().then()
    }
  })

  onCleanup(() => {
    setIsInitialized(false)
  })

  return (
    <>
      <style>{customCss()}</style>
      <style>{immutableCss}</style>
      <Show when={error()} keyed>
        {(error) => <ErrorMessage error={error} />}
      </Show>
      <Show when={initialChatReply()} keyed>
        {(initialChatReply) => (
          <BotContent
            botName={props.botName}
            class={props.class}
            initialChatReply={{
              ...initialChatReply,
              bot: {
                ...initialChatReply.bot,
                settings:
                  typeof props.bot === 'string'
                    ? initialChatReply.bot?.settings
                    : props.bot?.settings,
                theme:
                  typeof props.bot === 'string'
                    ? initialChatReply.bot?.theme
                    : props.bot?.theme,
              },
            }}
            context={{
              apiHost: props.apiHost,
              source: props.source,
              isPreview:
                typeof props.bot !== 'string' || (props.isPreview ?? false),
              resultId: initialChatReply.resultId,
              sessionId: initialChatReply.sessionId,
              bot: initialChatReply.bot,
              storage:
                initialChatReply.bot.settings.general?.rememberUser
                  ?.isEnabled &&
                  !(
                    typeof props.bot !== 'string' ||
                    (props.isPreview ?? false)
                  )
                  ? initialChatReply.bot.settings.general?.rememberUser
                    ?.storage ?? defaultSettings.general.rememberUser.storage
                  : undefined,
            }}
            progressBarRef={props.progressBarRef}
            onNewInputBlock={props.onNewInputBlock}
            onNewLogs={props.onNewLogs}
            onAnswer={props.onAnswer}
            onEnd={props.onEnd}
          />
        )}
      </Show>
    </>
  )
}

type BotContentProps = {
  botName?: string | undefined
  initialChatReply: StartChatResponse
  context: BotContext
  class?: string
  progressBarRef?: HTMLDivElement
  onNewInputBlock?: (inputBlock: InputBlock) => void
  onAnswer?: (answer: { message: string; blockId: string }) => void
  onEnd?: () => void
  onNewLogs?: (logs: OutgoingLog[]) => void
}

const BotContent = (props: BotContentProps) => {


  const [progressValue, setProgressValue] = persist(
    createSignal<number | undefined>(props.initialChatReply.progress),
    {
      storage: props.context.storage,
      key: `bot-${props.context.bot.id}-progressValue`,
    }
  )

  const [botContainerWidth, setBotContainerWidth] = createSignal<string>('100%')


  let botContainerElement: HTMLDivElement | undefined

  const resizeObserver = new ResizeObserver((entries) => {
    setBotContainerWidth(`${entries[0].contentRect.width - 60}px`)
    return setIsMobile(entries[0].target.clientWidth < 400)

  })

  onMount(() => {
    if (!botContainerElement) return
    setBotContainer(botContainerElement)
    resizeObserver.observe(botContainerElement)
    setBotContainerWidth(`${botContainerElement.clientWidth - 60}px`)
    setBotContainerHeight(`${botContainerElement.clientHeight}px`)
  })

  createEffect(() => {
    injectFont(
      props.initialChatReply.bot.theme.general?.font ?? {
        type: defaultFontType,
        family: defaultFontFamily,
      }
    )
    if (!botContainerElement) return
    setCssVariablesValue(
      props.initialChatReply.bot.theme,
      botContainerElement,
      props.context.isPreview
    )

  })

  onCleanup(() => {
    if (!botContainerElement) return
    resizeObserver.unobserve(botContainerElement)
  })

  return (
    <div
      ref={botContainerElement}
      class={clsx(
        'relative flex w-full h-full text-base overflow-hidden flex-col justify-between items-center bot-container ',
        props.class
      )}
    >
      <div class='flex h-full w-full flex-col items-center overflow-hidden'>
        <div class={clsx('px-[30px] py-5 w-full bg-primary-gradient text-left')}>
          <p class='text-xl text-white'>
            {props.botName ?? 'Bot'}
          </p>
        </div>
        <Show
          when={
            isDefined(progressValue()) &&
            props.initialChatReply.bot.theme.general?.progressBar?.isEnabled
          }
        >
          <Show
            when={
              props.progressBarRef &&
              (props.initialChatReply.bot.theme.general?.progressBar
                ?.position ?? defaultProgressBarPosition) === 'fixed'
            }
            fallback={<ProgressBar value={progressValue() as number} />}
          >
            <Portal mount={props.progressBarRef}>
              <ProgressBar value={progressValue() as number} />
            </Portal>
          </Show>
        </Show>
        <ConversationContainer
          data={sharedStringValue.value as ConversationData}
          botContainerWidth={botContainerWidth()}
          context={props.context}
          initialChatReply={props.initialChatReply}
          onNewInputBlock={props.onNewInputBlock}
          onAnswer={props.onAnswer}
          onEnd={props.onEnd}
          onNewLogs={props.onNewLogs}
          onProgressUpdate={setProgressValue}
        />
      </div>
      <Show
        when={
          props.initialChatReply.bot.settings.general?.isBrandingEnabled
        }
      >
        <LiteBadge botContainer={botContainerElement} />
      </Show>
      <Toaster toaster={toaster}>
        {(toast) => (
          <Toast.Root>
            <Toast.Title>{toast().title}</Toast.Title>
            <Toast.Description>{toast().description}</Toast.Description>
            <Toast.CloseTrigger class="absolute right-2 top-2">
              <CloseIcon class="h-4 w-4" />
            </Toast.CloseTrigger>
          </Toast.Root>
        )}
      </Toaster>
    </div>
  )
}
