import type { Component, ParentComponent } from 'solid-js'
/* eslint-disable ts/no-use-before-define */
import { createLazyMemo } from '@solid-primitives/memo'
import { MetaProvider, Title } from '@solidjs/meta'
import { HashRouter, Navigate, Route, useIsRouting, useLocation, useSearchParams } from '@solidjs/router'
import { Logger } from 'besonders-logger'
import classNames from 'classnames'
import capitalize from 'lodash-es/capitalize'
import { createEffect, createMemo, createResource, createSignal, lazy, on, onCleanup, Show, Suspense, untrack } from 'solid-js'
import { appInit, initialized, setInitialized } from './appInit'
import { BackLink } from './components/BackLink'
import { ExtraSearchResults, HeaderSearchBar } from './components/bars/SearchBar'
import { TopBar } from './components/bars/TopBar'
import { BlockSettingsDialog } from './components/BlockSettings'
import { ShareChooser } from './components/BulletMenu'
import { RedirectWorkaround, Spinner } from './components/mini-components'
import { PwaReloadPrompt } from './components/PwaReloadPrompt'
import { getApplogDB, initApplogDB } from './data/ApplogDB'
import { SearchContext, useExtraSearchResults } from './data/search'
import { BlockVM } from './data/VMs/BlockVM'
import { useVM } from './data/VMs/MappedVMbase'
import { AgentSetupPage, shouldShowAgentSetup } from './pages/AgentSetupPage'
import { Help } from './pages/HelpPage'
import { InspectPage } from './pages/InspectPage'
import { useAppSettings } from './ui/app-settings'

import { DBContext, withDS } from './ui/reactive'
import { createDebouncedWithEqCheck, replaceUriParts, useSingleUrlParam } from './ui/utils-ui'
import './data/devtools-helpers'

const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO, { prefix: '[App]' }) // eslint-disable-line unused-imports/no-unused-vars

const MAX_BLOCK_TITLE_LENGTH = 20

// import MainPage from './MainPage'
// import SettingsPage from './SettingsPage'
// import TestPage from './TestPage'
const MainPage = lazy(async () => ({ default: (await import('./pages/MainPage')).MainPage }))
const SettingsPage = lazy(() => import('./pages/SettingsPage'))
const [searchOpen, setSearchOpen] = createSignal(false) // TODO: refactor to search provider

export const [blockSettingsID, setBlockSettingsID] = createSignal('')
export function showBlockSettings(blkID) {
	setBlockSettingsID(blkID)
}
export function hideBlockSettings() {
	setBlockSettingsID('')
}

export const AppRoot: Component = () => {
	// const isPreview = createMemo(() => (window.location.hash.includes('preview=') || window.location.hash.includes('search='))) // HACK

	const homeBlockUrl = createLazyMemo(() => { // lazy so the errors are not thrown
		DEBUG('calc homeBlockUrl', initialized()) // only if url is explicitly #/home
		if (!initialized()) return null
		const appSettings = withDS(getApplogDB(), () => useAppSettings())
		if (!appSettings.homeBlock) throw ERROR(`missing appsetting: homeBlockUrl`, appSettings)
		if (!appSettings.homeBlock) {
			WARN(`missing appsetting: homeBlockUrl`, appSettings)
			return '/timeline'
		}
		return `/block/${appSettings.homeBlock}`
	})

	return (
		<HashRouter root={App}>
			<Route
				path='/'
				component={StartPageRedirect}
			/>
			<Route
				path='/home'
				component={() => (
					<Show when={homeBlockUrl()}>
						<Navigate href={homeBlockUrl()} />
					</Show>
				)}
			/>
			<Route
				path={['/new', '/timeline', '/block/:blockID']}
				component={() => {
					const location = useLocation()
					const [searchParams] = useSearchParams()
					const needsAgentSetup = createMemo(() => !searchParams.preview && !searchParams.zen && shouldShowAgentSetup())
					return (
						<Show
							when={!needsAgentSetup()}
							fallback={<Navigate href={`/setup?redirect=${encodeURIComponent(location.pathname + location.search + location.hash)}`} />}
						>
							<MainPage searchOpen={searchOpen} />
						</Show>
					)
				}}
			/>

			<Route
				path={'/settings/*section'}
				component={() => <SettingsPage />}
			/>
			<Route
				path='/help'
				component={() => <Help />}
			/>
			<Route
				path='/setup'
				component={AgentSetupPage}
			/>
			<Route
				path='/inspect'
				component={InspectPage}
			/>

			<Route
				path='*'
				component={() => (
					<div w-full>
						<BackLink />
						<h1>404 - page not found</h1>
					</div>
				)}
			/>
		</HashRouter>
	)
}
const StartPageRedirect: Component = () => {
	const location = useLocation()
	const [searchParams] = useSearchParams()
	const isPreviewOrSearch = createMemo(() => !!searchParams.preview || !!searchParams.search)
	// const isPreview = createMemo(() => (window.location.hash.includes('preview=') || window.location.hash.includes('search='))) // HACK

	const redirectUrl = createMemo(() => {
		if (!initialized()) return null

		const appSettings = withDS(getApplogDB(), () => useAppSettings())
		const startPage: string = appSettings.startPage
		let newPathname
		// if (isPreviewOrSearch) newPathname = '/timeline'
		// else {
		DEBUG({ startPage, appSettings })
		if (startPage === 'home-block') {
			newPathname = '/home'
		} else if (startPage === 'empty-block') {
			newPathname = '/new'
		} else if (startPage === 'timeline') {
			newPathname = '/timeline'
		} else {
			WARN(`unknown startPage (using it either way): `, startPage)
			newPathname = startPage
		}
		// }
		return replaceUriParts(location, { pathname: newPathname }) // to keep preview= & co
	})
	return (
		<>
			{!isPreviewOrSearch()
				? (
						<Show when={redirectUrl()}>
							{/* <Navigate href={redirectUrl()} /> Sadly broken... */}
							<RedirectWorkaround url={redirectUrl()} />
						</Show>
					)
				: (
						<Suspense fallback={<Spinner size='3rem' color='green' />}>
							<MainPage searchOpen={searchOpen} />
						</Suspense>
					)}
		</>
	)
}

let renderCount = 0
export const App: ParentComponent = (props) => {
	;(renderCount++ > 0 ? WARN : DEBUG)('!!! APP RENDER !!!', renderCount)
	// trace(true)

	const [searchParams] = useSearchParams()
	const pubFromUrl = createMemo(() => searchParams.pub)
	const location = useLocation()
	// const navigate = useNavigate()
	const isRouting = useIsRouting()
	const isPreview = createMemo(() => !!searchParams.preview)
	const isZenview = createMemo(() => !!searchParams.zen)
	// const isZenview = createMemo(() => location.pathname.startsWith('/zenview'))

	const [searchInUrl, setSearchInUrl] = useSingleUrlParam('search')
	const [search, setSearch] = createSignal(untrack(() => searchInUrl()))

	//////////
	// INIT //
	//////////
	const cleanup = new Set<() => void>()
	const [applogDB] = createResource(async () => {
		LOG('[App] Initializing DB')
		const db = await initApplogDB()
		LOG('[App] DB init done', db)
		const cleanUp = await appInit({ pubFromUrl: untrack(() => pubFromUrl()) })
		cleanup.add(cleanUp)
		LOG('[App] cleanup ready', cleanup)
		setInitialized(true)
		return db
	})
	onCleanup(() => {
		for (const eachCleanup of cleanup) {
			eachCleanup()
		}
	})

	const setSearchInUrlDebounced = createDebouncedWithEqCheck(
		searchInUrl, // will only update if still the same after debounce
		(newSearch: string) => {
			DEBUG(`debounced setSearchInUrl:`, newSearch)
			setSearchInUrl(
				newSearch,
				// { replace: !!searchParams.search == !!newSearch } // we debounce already, so... new entry
			)
		},
		1400, // ? after which time of having a search entry should it have it's own separate history entry?
	)
	createEffect(on(() => searchInUrl(), (searchInUrl) => {
		DEBUG(`searchInUrl changed to`, searchInUrl, { searchInState: search() })
		if (searchInUrl !== search()) {
			// changed from browser navigation
			setSearch(searchInUrl || null)
			setSearchOpen(!!searchInUrl)
		}
	}))
	createEffect(on(() => search(), (search) => {
		DEBUG(`search changed to`, search)
		// if (!search) setSearchOpen(false)
		if (search !== null && search !== undefined) {
			setSearchInUrlDebounced(search)
		}
	}))
	createEffect(on(() => searchOpen(), (searchOpen) => {
		DEBUG(`searchOpen changed to`, searchOpen, { search: search() })
		if (!searchOpen) {
			setSearch(null)
			setSearchInUrl(null)
			setSearchInUrlDebounced.cancel()
		}
	}))
	const searchResults = createMemo(() => {
		if (!applogDB()) return null
		const results = withDS(applogDB(), () => useExtraSearchResults({ search$: search }))()
		DEBUG(`<App>.searchResults`, results)
		return results
	})

	const title = createMemo(() => {
		const location = useLocation()
		const pathname = createMemo(() => location.pathname.slice(1))
		DEBUG({ pathname: pathname() })
		if (searchOpen()) return `Search '${search()}'`
		if (pathname().startsWith('block')) {
			const blockIDmemo = createMemo(() => pathname().split('block/')[1])
			const blockContent = initialized() ? useVM(BlockVM, blockIDmemo, applogDB())().contentPlaintext ?? 'empty block' : ''
			const trimmedContent = blockContent.length > MAX_BLOCK_TITLE_LENGTH
				? `${blockContent.substring(0, MAX_BLOCK_TITLE_LENGTH)}…`
				: blockContent
			return trimmedContent
		}
		if (pathname()) return `${capitalize(pathname())}`
		return null
	})

	const updateSchoelaceDarkmode = (enabled: boolean) => document.documentElement.classList.toggle('sl-theme-dark', enabled)
	const prefersDarkQuery = window.matchMedia('(prefers-color-scheme: dark)')
	updateSchoelaceDarkmode(window.matchMedia && prefersDarkQuery.matches)
	prefersDarkQuery.addEventListener('change', (event) => {
		LOG(`Dark mode`, event.matches)
		updateSchoelaceDarkmode(event.matches)
	})

	LOG('App rendering', location.pathname)
	return (
		<DBContext.Provider value={applogDB}>
			<SearchContext.Provider value={{ search, setSearch }}>
				<MetaProvider>
					<Title>{title() ? `${title()} - ` : ''}Note3</Title>
					{/* <OverlaySearchBar open={searchOpen} setOpen={setSearchOpen} /> */}
					<Show
						when={initialized()}
					>
						<ShareChooser />

						{/* <Show when={!!blockSettingsID()}> */}
						<BlockSettingsDialog blockID={blockSettingsID()} />
						{/* </Show> */}
					</Show>

					<Show when={isRouting()}>
						<div absolute inset-0 bg='black opacity-40' flex='~ col items-center justify-center ' z-1000>
							{/* (i) workaround for https://github.com/solidjs/solid-router/discussions/296 */}
							<Spinner size='3rem' color='red' m-12 />
							{
								/* <div animate='property-[opacity] fade-in duration-[3s]' text-red>
								stuck? <a href='#' underline visited:color-inherit onClick={() => window.location.reload()}>reload</a>
							</div> */
							}
						</div>
					</Show>

					<div
						id='main-container'
						flex='~ col items-center justify-start'
						class='min-h-100vh bg-basebg color-text'
						px-2
						border-box
						h-100vh
						overflow-y-scroll
						overflow-x-clip
					>
						<div
							flex='~ col items-stretch justify-start'
							class={classNames(['box-border max-w-4xl min-h-100vh w-full bg-basebg color-text', isZenview() ? 'pt-4' : ''])}
							gap-2
						>
							<Show when={!isZenview()}>
								<div my-2>
									<div
										id='header-search-wrapper'
										class={searchOpen() ? 'flex' : 'hidden'}
										w-full
										flex='items-center justify-between'
										relative
										gap-2
									>
										{
											/* <h2 m-0 flex='inline items-center gap-4'>
											<BackLink text='' onClick={() => setSearchOpen(false)} />
										</h2> */
										}
										<HeaderSearchBar open={searchOpen()} setOpen={setSearchOpen} firstResult={searchResults()?.[0]} />
									</div>
									<TopBar searchOpen={searchOpen} setSearchOpen={setSearchOpen} />
								</div>
							</Show>

							<PwaReloadPrompt class='w-full' />

							<Show
								when={initialized()}
								fallback={(
									<div flex='~ justify-center' w-full>
										{/* <pre>innit? {JSON.stringify(initialized())}</pre> */}
										<Spinner size='3rem' color='purple' m-12 />
									</div>
								)}
							>
								<Show when={searchResults()?.length}>
									<ExtraSearchResults results={searchResults()} close={() => setSearchOpen(false)} />
								</Show>
								<Show when={!searchResults() || searchResults().length === 0}>
									<Suspense
										fallback={(
											<div flex='~ col items-center justify-start'>
												<Spinner size='3rem' color='green' m-12 />
											</div>
										)}
									>
										{props.children /* Routes */}
									</Suspense>
								</Show>
							</Show>
						</div>
					</div>
				</MetaProvider>
			</SearchContext.Provider>
		</DBContext.Provider>
	)
}
