<script lang="ts">
	import { onMount } from "svelte"
	import { Marked } from "marked"
	import { markedHighlight } from "marked-highlight"
	import hljs from "highlight.js"
	import { cError, versionHistory } from "$lib/logic/client"
	import ChevronDown from "$lib/icons/ChevronDown.svelte"
	import ChevronUp from "$lib/icons/ChevronUp.svelte"
	import EditPen from "$lib/icons/editor/EditPen.svelte"
	import ViewEye from "$lib/icons/editor/ViewEye.svelte"
	import Save from "$lib/icons/editor/Save.svelte"
	import type { FullNote } from "$lib/logic/model"
	import { formatTitleWithHighlight } from "$lib/util/contentVisual"

	// props
	export let note: FullNote
	export let isEditing = false
	export let isVersionDropdownOpen = false
	export let saveNote: (title: string, content: string) => Promise<void>
	export let selectVersion: (versionID: string, isActiveVersion: boolean) => Promise<void>

	// constants
	const TITLE_MAX_LENGTH = 150
	const ZEROWIDTH_CHARACTERS = /^[\u200B\u200C\u200D\u200E\u200F\uFEFF]/

	// local state
	let editableTitle = note.title
	let editableContent = note.content
	let displayTitle = formatTitleWithHighlight(note.title)

	// markdown parser with syntax highlight support
	const marked = new Marked(
		markedHighlight({
			emptyLangClass: "hljs",
			langPrefix: "hljs language-",
			highlight(code, lang, info) {
				const language = hljs.getLanguage(lang) ? lang : "plaintext"
				// console.log(`syntax highlight language: ${language}`)
				return hljs.highlight(code, { language }).value
			}
		})
	)

	// update the local copy when the note changes
	$: if (note && note.id) {
		editableTitle = note.title
		editableContent = note.content
		displayTitle = formatTitleWithHighlight(note.title)
	}

	const handleContentChange = (
		event: Event & { currentTarget: EventTarget & HTMLTextAreaElement }
	) => {
		if (!event.target) {
			return
		}

		const { value } = event.target as HTMLTextAreaElement
		editableContent = value

		// update title based on the first line if it starts with #
		const firstLine = editableContent.split("\n")[0]
		if (firstLine && firstLine.startsWith("# ")) {
			editableTitle = firstLine.substring(2).slice(0, TITLE_MAX_LENGTH)
		} else if (!editableTitle || editableTitle === "Untitled Note") {
			// try to extract a title from the first non-empty line if there's no title yet
			const firstNonEmptyLine = editableContent.split("\n").find((line) => line.trim().length > 0)

			if (firstNonEmptyLine) {
				editableTitle =
					firstNonEmptyLine.length > TITLE_MAX_LENGTH
						? firstNonEmptyLine.substring(0, TITLE_MAX_LENGTH - 3) + "..."
						: firstNonEmptyLine
			}
		}
	}

	const isLatestVersion = (note: FullNote | null) => {
		if (!note) {
			return true
		}

		return note.isActiveVersion
	}

	const toggleEditMode = () => {
		if (isLatestVersion(note)) {
			isEditing = !isEditing
			isVersionDropdownOpen = false
		} else {
			$cError = "Editing historical versions is prohibited."
		}
	}

	const exitEditMode = () => {
		isEditing = false
	}

	const toggleVersionDropdown = () => {
		isVersionDropdownOpen = !isVersionDropdownOpen
	}

	const handleKeyDown = (event: KeyboardEvent) => {
		// ctrl+enter or cmd+enter keyboard shortcut for saving the current note
		if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
			event.preventDefault()
			handleSave()
		}
		// esc keyboard shortcut for quitting edit mode
		else if (event.key === "Escape") {
			event.preventDefault()
			exitEditMode()
		}
	}

	const handleSave = () => {
		// only update the display title once the user saves the edits
		displayTitle = formatTitleWithHighlight(editableTitle)
		saveNote(editableTitle, editableContent)
	}

	const handleTitleChange = (event: Event & { currentTarget: EventTarget & HTMLInputElement }) => {
		if (!event.target) {
			return
		}

		const { value } = event.target as HTMLInputElement
		editableTitle = value.slice(0, TITLE_MAX_LENGTH)
	}

	const parseMarkdown = async (markdown: string) => {
		if (!markdown) {
			return ""
		}

		// github flavored markdown rendering
		marked.setOptions({
			breaks: false,
			gfm: true,
			pedantic: false
		})

		// remove the most common zerowidth characters from the start of the file
		marked.parse(markdown.replace(ZEROWIDTH_CHARACTERS, ""))

		return await marked.parse(markdown)
	}

	let textarea: HTMLTextAreaElement | null
	const autoResize = () => {
		if (textarea) {
			textarea.style.height = "auto"
			textarea.style.height = textarea.scrollHeight + "px"
		}
	}

	onMount(() => {
		if (isEditing && textarea) {
			// scrollbar is hidden in global CSS so flickering during resizing of the
			// textarea shouldn't be an issue anymore
			autoResize()
		}

		// close the versioning dropdown menu if it has been left open in the previously viewed notes
		isVersionDropdownOpen = false
	})

	$: if (isEditing && textarea) {
		setTimeout(autoResize, 0)
	}
</script>

<div class="note-editor-content">
	<!-- Note title -->
	<div class="note-title-container">
		{#if isEditing}
			<input
				type="text"
				bind:value={editableTitle}
				on:input={handleTitleChange}
				on:keydown={handleKeyDown}
				placeholder="Title"
				class="note-title-input"
			/>
			<div class="note-char-count ml-2">
				{editableTitle.length}/{TITLE_MAX_LENGTH} characters
			</div>
		{:else}
			<h1 class="note-title-display">
				{@html displayTitle}
			</h1>
		{/if}

		<!-- TODO: add pagination support for versions dropdown -->

		<!-- Note action buttons -->
		<div class="note-action-container">
			<!-- Editor mode toggle button -->
			{#if isLatestVersion(note)}
				<button on:click={toggleEditMode} class="note-action-button w-28">
					{#if isEditing}
						<EditPen classString="mr-3 h-4 w-4" />
					{:else}
						<ViewEye classString="mr-3 h-4 w-4" />
					{/if}
					{isEditing ? "Editing" : "Viewing"}
				</button>
			{:else}
				<button disabled class="note-action-button w-28 cursor-not-allowed opacity-40"
					>Viewing</button
				>
			{/if}

			<!-- Versioning dropdown -->
			<div class="relative flex flex-col">
				<button on:click={toggleVersionDropdown} class="note-action-button min-w-32">
					Version {note.versionNumber - 1}

					{#if isVersionDropdownOpen}
						<ChevronUp classString="ml-1.5 h-4 w-4" />
					{:else}
						<ChevronDown classString="ml-1.5 h-4 w-4" />
					{/if}
				</button>

				{#if isVersionDropdownOpen && $versionHistory && $versionHistory.length > 0}
					<div class="note-versions-dropdown">
						{#each $versionHistory as version, index}
							<button
								on:click={() => selectVersion(version.versionID, version.isActive)}
								class="sidebar-action-button w-full flex-col rounded-none {index === 0
									? 'rounded-t-lg'
									: ''} {index === $versionHistory.length - 1 ? 'rounded-b-lg' : ''}
                                    {version.isActive ? 'versions-dropdown-active-version' : ''}"
							>
								<p class="versions-dropdown-item-text">{version.title}</p>
								<span class="versions-dropdown-item-meta"
									>{version.createdAt.toLocaleString()}, v.{version.versionNumber}</span
								>
							</button>
						{/each}
					</div>
				{/if}
			</div>

			{#if isEditing}
				<button
					on:click={() => saveNote(editableTitle, editableContent)}
					class="note-action-icon-button"
				>
					<Save classString="h-5 w-5" />
				</button>
			{/if}
		</div>
	</div>

	<!-- Note content (takes up remaining vertical space) -->
	<div class="note-editor-container">
		{#if isEditing}
			<div class="note-editor-wrapper">
				<textarea
					bind:this={textarea}
					bind:value={editableContent}
					on:input={handleContentChange}
					on:keydown={handleKeyDown}
					placeholder="Markdown contents"
					class="note-textarea"
				></textarea>

				<button
					on:click={() => saveNote(editableTitle, editableContent)}
					class="note-mobile-save-button"
				>
					<Save classString="h-6 w-6" />
				</button>
			</div>
		{:else}
			<!-- Rendered markdown preview -->
			<div class="prose markdown-preview max-w-none">
				{#await parseMarkdown(note.content) then html}
					{@html html}
				{/await}
			</div>
		{/if}
	</div>
</div>
