import { createContext, useCallback, useEffect, useState } from 'react'
import JsonNode from './json-node'
import type { Collapsed, CustomizeCollapseStringUI, CustomizeNode, DisplaySize, Editable } from '../types'
import { stringifyForCopying } from '../utils'

type OnEdit = (params: { newValue: any; oldValue: any; depth: number; src: any; indexOrName: string | number; parentType: 'object' | 'array' | null }) => void
type OnDelete = (params: { value: any; indexOrName: string | number; depth: number; src: any; parentType: 'object' | 'array' | null }) => void
type OnAdd = (params: { indexOrName: string | number; depth: number; src: any; parentType: 'object' | 'array' }) => void
type OnChange = (params: {
	indexOrName: string | number
	depth: number
	src: any
	parentType: 'object' | 'array' | null
	type: 'add' | 'edit' | 'delete'
}) => void

export const defaultURLRegExp = /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/

export const JsonViewContext = createContext({
	src: undefined as any,

	collapseStringsAfterLength: 99,
	collapseStringMode: 'directly' as 'directly' | 'word' | 'address',
	customizeCollapseStringUI: undefined as CustomizeCollapseStringUI | undefined,

	collapseObjectsAfterLength: 20,
	collapsed: false as Collapsed,

	enableClipboard: true,

	editable: false as Editable,
	onEdit: undefined as OnEdit | undefined,
	onDelete: undefined as OnDelete | undefined,
	onAdd: undefined as OnAdd | undefined,
	onChange: undefined as OnChange | undefined,

	forceUpdate: () => {},

	customizeNode: undefined as CustomizeNode | undefined,
	customizeCopy: (() => {}) as (node: any) => any,

	displaySize: undefined as DisplaySize,

	matchesURL: false,
	urlRegExp: defaultURLRegExp,

	ignoreLargeArray: false
})

export interface JsonViewProps {
	src: any

	collapseStringsAfterLength?: number
	collapseStringMode?: 'directly' | 'word' | 'address'
	customizeCollapseStringUI?: CustomizeCollapseStringUI

	collapseObjectsAfterLength?: number
	collapsed?: Collapsed

	enableClipboard?: boolean

	editable?: Editable
	onEdit?: OnEdit
	onDelete?: OnDelete
	onAdd?: OnAdd
	onChange?: OnChange

	customizeNode?: CustomizeNode
	customizeCopy?: (node: any) => any

	dark?: boolean
	theme?: 'default' | 'a11y' | 'github' | 'vscode' | 'atom' | 'winter-is-coming'

	displaySize?: DisplaySize

	style?: React.CSSProperties
	className?: string

	matchesURL?: boolean
	urlRegExp?: RegExp

	ignoreLargeArray?: boolean
}

export default function JsonView({
	src: _src,

	collapseStringsAfterLength = 99,
	collapseStringMode = 'directly',
	customizeCollapseStringUI,

	collapseObjectsAfterLength = 99,
	collapsed,

	enableClipboard = true,

	editable = false,
	onEdit,
	onDelete,
	onAdd,
	onChange,

	dark = false,
	theme = 'default',

	customizeNode,
	customizeCopy = stringifyForCopying,

	displaySize,

	style,
	className,

	matchesURL = false,
	urlRegExp = defaultURLRegExp,

	ignoreLargeArray = false
}: JsonViewProps) {
	const [_, update] = useState(0)
	const forceUpdate = useCallback(() => update(state => ++state), [])
	const [src, setSrc] = useState(_src)
	useEffect(() => setSrc(_src), [_src])

	return (
		<JsonViewContext.Provider
			value={{
				src,

				collapseStringsAfterLength,
				collapseStringMode,
				customizeCollapseStringUI,

				collapseObjectsAfterLength,
				collapsed,

				enableClipboard,

				editable,
				onEdit,
				onDelete,
				onAdd,
				onChange,

				forceUpdate,

				customizeNode,
				customizeCopy,

				displaySize,

				matchesURL,
				urlRegExp,

				ignoreLargeArray
			}}>
			<code
				className={'json-view' + (dark ? ' dark' : '') + (theme && theme !== 'default' ? ' json-view_' + theme : '') + (className ? ' ' + className : '')}
				style={style}>
				<JsonNode
					node={src}
					depth={1}
					editHandle={(indexOrName: number | string, newValue: any, oldValue: any) => {
						setSrc(newValue)
						if (onEdit)
							onEdit({
								newValue,
								oldValue,
								depth: 1,
								src,
								indexOrName: indexOrName,
								parentType: null
							})
						if (onChange) onChange({ type: 'edit', depth: 1, src, indexOrName: indexOrName, parentType: null })
					}}
					deleteHandle={() => {
						setSrc(undefined)
						if (onDelete)
							onDelete({
								value: src,
								depth: 1,
								src,
								indexOrName: '',
								parentType: null
							})
						if (onChange)
							onChange({
								depth: 1,
								src,
								indexOrName: '',
								parentType: null,
								type: 'delete'
							})
					}}
				/>
			</code>
		</JsonViewContext.Provider>
	)
}
