//
// Create a set of Extensions that provide syntax highlighting.
//


import { defaultHighlightStyle, syntaxHighlighting, HighlightStyle } from '@codemirror/language';
import { tags } from '@lezer/highlight';
import { EditorView } from '@codemirror/view';
import { Extension } from '@codemirror/state';

import { inlineMathTag, mathTag } from './extensions/markdownMathExtension';
import { EditorTheme } from '../types';

// For an example on how to customize the theme, see:
//
// https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts
//
// For a tutorial, see:
//
// https://codemirror.net/6/examples/styling/#themes
//
// Use Safari developer tools to view the content of the CodeMirror iframe while
// the app is running. It seems that what appears as ".ͼ1" in the CSS is the
// equivalent of "&" in the theme object. So to target ".ͼ1.cm-focused", you'd
// use '&.cm-focused' in the theme.
//
// [theme] should be a joplin theme (see @joplin/lib/theme)
const createTheme = (theme: EditorTheme): Extension[] => {
	// If the theme hasn't loaded yet, return nothing.
	// (createTheme should be called again after the theme has loaded).
	if (!theme) {
		return [];
	}

	const isDarkTheme = theme.appearance === 'dark';

	const baseGlobalStyle: Record<string, string> = {
		color: theme.color,
		backgroundColor: theme.backgroundColor,

		// On iOS, apply system font scaling (e.g. font scaling
		// set in accessibility settings).
		font: '-apple-system-body',

		// Fill container horizontally
		width: '100%',
		boxSizing: 'border-box',
	};
	const baseCursorStyle: Record<string, string> = { };
	const baseContentStyle: Record<string, string> = {
		fontFamily: theme.fontFamily,
		fontSize: `${theme.fontSize}${theme.fontSizeUnits ?? 'px'}`,

		// Avoid using units here -- 1.55em, for example, can cause lines to overlap
		// if some lines contain text with a large enough font size.
		lineHeight: theme.isDesktop ? '1.55' : undefined,
	};
	const baseSelectionStyle: Record<string, string> = { };
	const blurredSelectionStyle: Record<string, string> = { };

	// If we're in dark mode, the caret and selection are difficult to see.
	// Adjust them appropriately
	if (isDarkTheme) {
		// Styling the caret requires styling both the caret itself
		// and the CodeMirror caret.
		// See https://codemirror.net/6/examples/styling/#themes
		baseContentStyle.caretColor = 'white';
		baseCursorStyle.borderLeftColor = 'white';

		baseSelectionStyle.backgroundColor = '#6b6b6b';
		blurredSelectionStyle.backgroundColor = '#444';
	}

	const monospaceStyle = {
		fontFamily: theme.monospaceFont || 'monospace',
	};

	// This is equivalent to the default selection style -- our styling must
	// be at least this specific.
	const selectionBackgroundSelector = '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground';

	// Matches the editor only when there are no gutters (e.g. line numbers) added by
	// plugins
	const editorNoGuttersSelector = '&:not(:has(> .cm-scroller > .cm-gutters))';

	const baseHeadingStyle = {
		fontWeight: 'bold',
		fontFamily: theme.fontFamily,
		paddingBottom: '0.2em',
	};

	const codeMirrorTheme = EditorView.theme({
		// Include &.CodeMirror to handle the case where additional CodeMirror 5 styles
		// need to be overridden.
		'&, &.CodeMirror': baseGlobalStyle,

		'& .cm-dropCursor': {
			backgroundColor: isDarkTheme ? 'white' : 'black',
			width: '1px',
		},

		// These must be !important or more specific than CodeMirror's built-ins
		'& .cm-content': {
			fontFamily: theme.fontFamily,
			...baseContentStyle,
			paddingBottom: theme.isDesktop ? '400px' : undefined,
			marginLeft: `${theme.marginLeft}px`,
			marginRight: `${theme.marginRight}px`,
		},

		'&.cm-focused .cm-cursor': baseCursorStyle,

		// The desktop app sets the font for these elements to a specific font.
		// Override this.
		'& div, & span, & a': {
			fontFamily: 'inherit',
		},

		// Override the default border around CodeMirror panels
		'& > .cm-panels': {
			border: 'none',
		},

		// &.cm-focused is used to give these styles higher specificity
		// than the defaults.
		[selectionBackgroundSelector]: baseSelectionStyle,
		'&.cm-focused ::selection': baseSelectionStyle,
		'& ::selection': blurredSelectionStyle,
		'& .cm-selectionLayer .cm-selectionBackground': blurredSelectionStyle,

		'&.cm-editor.cm-focused': {
			outline: 'none !important',
		},

		'& .cm-blockQuote': {
			borderLeft: `4px solid ${theme.colorFaded}`,
			opacity: theme.blockQuoteOpacity,
			paddingLeft: '4px',
		},

		'& .cm-codeBlock': {
			backgroundColor: 'rgba(155, 155, 155, 0.07)',
			...monospaceStyle,
		},

		// CodeMirror wraps the existing inline span in an additional element.
		// Due to a Chrome rendering bug, because the .cm-inlineCode wraps a
		// span with a larger font-size, the .cm-inlineCode's bounding box won't
		// be big enough for its content.
		// As such, we need to style whichever element directly wraps its content.
		'& .cm-inlineCode': {
			borderWidth: '1px',
			borderStyle: 'solid',
			borderColor: isDarkTheme ? 'rgba(200, 200, 200, 0.5)' : 'rgba(100, 100, 100, 0.5)',
			borderRadius: '4px',

			...monospaceStyle,
		},

		'& .cm-mathBlock, & .cm-inlineMath': {
			color: isDarkTheme ? '#9fa' : '#276',
		},

		'& .cm-tableHeader, & .cm-tableRow, & .cm-tableDelimiter': monospaceStyle,
		'& .cm-taskMarker': monospaceStyle,

		'&.cm-editor .cm-content': theme.contentMaxWidth ? {
			maxWidth: theme.contentMaxWidth,

			// Center
			marginLeft: 'auto',
			marginRight: 'auto',
		} : undefined,

		// Allows editor content to be left-aligned with the toolbar on desktop.
		// See https://github.com/laurent22/joplin/issues/11279
		[`${editorNoGuttersSelector} .cm-line`]: theme.isDesktop ? {
			// Note: This cannot be zero:
			paddingLeft: '1px',
		} : undefined,

		// Override the default URL style when the URL is within a link
		'& .tok-url.tok-link, & .tok-link.tok-meta, & .tok-link.tok-string': {
			opacity: 0.661,
		},

		'& .cm-strike': {
			textDecoration: 'line-through',
		},
		'& .cm-insert': {
			textDecoration: 'underline',
		},

		// Applying font size changes with CSS rather than the theme below works
		// around an issue where the border for code blocks in headings was too
		// small.
		'& .cm-h1': {
			...baseHeadingStyle,
			fontSize: '1.5em',
		},
		// Only underline level 1 headings not in block quotes. The underline overlaps with the blockquote border.
		'& .cm-h1:not(.cm-blockQuote)': {
			borderBottom: `1px solid ${theme.dividerColor}`,
			marginBottom: '0.1em',
		},
		'& .cm-h2': {
			...baseHeadingStyle,
			fontSize: '1.4em',
		},
		'& .cm-h3': {
			...baseHeadingStyle,
			fontSize: '1.3em',
		},
		'& .cm-h4': {
			...baseHeadingStyle,
			fontSize: '1.2em',
		},
		'& .cm-h5': {
			...baseHeadingStyle,
			fontSize: '1.1em',
		},
		'& .cm-h6': {
			...baseHeadingStyle,
			fontSize: '1.0em',
		},

		'& .cm-highlighted': {
			color: theme.searchMarkerColor,
			backgroundColor: theme.searchMarkerBackgroundColor,
		},

		// Style the search widget. Use ':root' to increase the selector's precedence
		// (override the existing preset styles).
		':root & .cm-panel.cm-search': {
			'& label, & button, & input': {
				fontSize: '1em',
				color: isDarkTheme ? 'white' : 'black',
			},
		},
	}, { dark: isDarkTheme });

	const highlightingStyle = HighlightStyle.define([
		{
			tag: tags.strong,
			fontWeight: 'bold',
		},
		{
			tag: tags.emphasis,
			fontStyle: 'italic',
		},
		{
			tag: tags.list,
			fontFamily: theme.fontFamily,
		},
		{
			tag: tags.comment,
			fontStyle: 'italic',
			color: isDarkTheme ? '#b18eb1' : '#6d7086',
		},
		{
			tag: tags.link,
			color: theme.urlColor,
		},
		{
			tag: [mathTag, inlineMathTag],
			fontStyle: 'italic',
		},

		// Content of code blocks. This should roughly match the colors used by the default
		// highlight.js theme in the note viewer, while also preserving at least 4.5:1 contrast.
		{
			tag: tags.keyword,
			color: isDarkTheme ? '#F92672' : '#a626a4',
		},
		{
			tag: tags.literal,
			color: isDarkTheme ? '#aaf' : '#037',
		},
		{
			tag: tags.number,
			color: isDarkTheme ? '#d19a66' : '#986801',
		},
		{
			tag: tags.typeName,
			color: isDarkTheme ? '#d19a66' : '#986801',
		},
		{
			tag: tags.inserted,
			color: isDarkTheme ? '#7f7' : '#471',
		},
		{
			tag: tags.deleted,
			color: isDarkTheme ? '#f96' : '#a21',
		},
		{
			tag: tags.propertyName,
			color: isDarkTheme ? '#61aeee' : '#406be5',
		},
		{
			tag: tags.string,
			color: isDarkTheme ? '#98c379' : '#50a14f',
		},
		{
			// CSS class names (and class names in other languages)
			tag: tags.className,
			color: isDarkTheme ? '#d8a' : '#904',
		},
		{
			tag: tags.macroName,
			color: isDarkTheme ? '#e6c07b' : '#986801',
		},
	]);

	return [
		codeMirrorTheme,
		syntaxHighlighting(highlightingStyle),

		// If we haven't defined highlighting for tags, fall back
		// to the default.
		syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
	];
};


export default createTheme;
