import { Signal, signal, useSignal } from '@preact/signals'
import { useEffect, useRef } from 'preact/hooks'
import { JSX } from 'preact'
import { JsonParseStream } from '$std/json/mod.ts'
import { CSS, render as renderMd } from 'gfm'

const systemHistory = signal<BotMessage[]>([{
	role: 'system',
	content:
		`Tu es un assistant spécialisé dans l'open source qui s'appel Coh@bot. Tu répond en français avec des réponse courtes. Tu aide les usagers du Fablab nommé Cohabit en leur proposant des solutions techniques appropriées en français. Tu formule des réponses courtes et précises en français et en markdown si besoin.`,
}])

const currentReader = signal<ReadableStreamDefaultReader<BotResponse> | null>(
	null,
)
let currentResponse: string[] = []

function MdCell({ children }: { children: Signal<string> }) {
	return (
		<div
			class='markdown-body'
			dangerouslySetInnerHTML={{ __html: renderMd(children.value) }}
		>
		</div>
	)
}

type BotMessage = {
	role: string
	content: string
}

type BotResponse = {
	model: string
	created_at: string
	message: {
		role: 'assistant' | 'user' | 'system'
		content: string
		images?: string
	}
	done: boolean
}

async function aiRequest(
	{ model, messages, stream }: {
		model: string
		messages: BotMessage[]
		stream: boolean
	},
) {
	systemHistory.value = [...systemHistory.peek(), ...messages]

	const body = JSON.stringify({
		model,
		messages: systemHistory,
		stream,
	})

	console.log('send', JSON.parse(body))

	const response = await fetch('http://localhost:11434/api/chat', {
		method: 'POST',
		headers: {
			'Content-Type': 'application/json; charset=utf-8',
		},
		body,
	})

	if (response.ok) {
		return response.body?.pipeThrough(new TextDecoderStream()).pipeThrough(
			new JsonParseStream(),
		) as ReadableStream<BotResponse> ?? new ReadableStream<BotResponse>()
	}

	throw new Error(response.statusText)
}

export default function AiChatBox() {
	const dialog = useRef<HTMLDialogElement>(null)
	const form = useRef<HTMLFormElement>(null)
	const history = useSignal<JSX.Element[]>([])

	useEffect(() => {
		dialog.current?.addEventListener('click', (event) => {
			if (event.target === dialog.current) {
				dialog.current?.close()
			}
		})

		form.current?.addEventListener(
			'submit',
			(event) => chatListener(event, history),
		)
	}, [])

	return (
		<>
			<style dangerouslySetInnerHTML={{ __html: CSS }}></style>
			<button
				class='islands__ai_chat_box__button'
				onClick={() => dialog.current?.showModal()}
			>
				<i class='ri-bard-line'></i>
			</button>
			<dialog ref={dialog} class='islands__ai_chat_box__dialog'>
				<div class='islands__ai_chat_box__dialog__content'>{history}</div>
				<form ref={form} class='islands__ai_chat_box__dialog__form'>
					<input
						type='text'
						name='query'
						placeholder='Saisissez une requête ...'
						autoFocus
						autoComplete='off'
					/>
					<button>
						<i class='ri-send-plane-2-line'></i>
					</button>
				</form>
			</dialog>
		</>
	)
}

async function chatListener(event: Event, history: Signal<JSX.Element[]>) {
	event.preventDefault()
	const form = event.target as HTMLFormElement

	const query = new FormData(form).get(
		'query',
	) as string

	form.reset()

	const userEntry = (
		<span class='islands__ai_chat_box__history__user'>{query}</span>
	)

	const botMessage = signal('')

	const botEntry = (
		<span class='islands__ai_chat_box__history_bot'>
			<MdCell>{botMessage}</MdCell>
		</span>
	)

	history.value = [...history.peek(), userEntry, botEntry]

	if (currentResponse.length !== 0) {
		systemHistory.value = [...systemHistory.peek(), {
			role: 'assistant',
			content: currentResponse.join(''),
		}]
		currentResponse = []
	}
	await currentReader.value?.cancel()

	const response = await aiRequest({
		model: 'mistral',
		messages: [{
			role: 'user',
			content: query,
		}],
		stream: true,
	})

	const reader = response.getReader()
	currentReader.value = reader

	while (true) {
		const { value, done } = await reader.read()
		currentResponse.push(value?.message.content ?? '')
		if (done) break
		console.log(value.message.content)
		botMessage.value = `${botMessage.peek()}${value.message.content}`
	}
}
