import type { DevToolbarApp, DevToolbarMetadata } from '../../../../@types/astro.js';
import type { DevToolbarHighlight } from '../ui-library/highlight.js';
import {
	attachTooltipToHighlight,
	createHighlight,
	getElementsPositionInDocument,
	positionHighlight,
} from './utils/highlight.js';
import { createWindowElement } from './utils/window.js';

const icon =
	'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#fff" d="M7.9 1.5v-.4a1.1 1.1 0 0 1 2.2 0v.4a1.1 1.1 0 1 1-2.2 0Zm-6.4 8.6a1.1 1.1 0 1 0 0-2.2h-.4a1.1 1.1 0 0 0 0 2.2h.4ZM12 3.7a1.1 1.1 0 0 0 1.4-.7l.4-1.1a1.1 1.1 0 0 0-2.1-.8l-.4 1.2a1.1 1.1 0 0 0 .7 1.4Zm-9.7 7.6-1.2.4a1.1 1.1 0 1 0 .8 2.1l1-.4a1.1 1.1 0 1 0-.6-2ZM20.8 17a1.9 1.9 0 0 1 0 2.6l-1.2 1.2a1.9 1.9 0 0 1-2.6 0l-4.3-4.2-1.6 3.6a1.9 1.9 0 0 1-1.7 1.2A1.9 1.9 0 0 1 7.5 20L2.7 5a1.9 1.9 0 0 1 2.4-2.4l15 5a1.9 1.9 0 0 1 .2 3.4l-3.7 1.6 4.2 4.3ZM19 18.3 14.6 14a1.9 1.9 0 0 1 .6-3l3.2-1.5L5.1 5.1l4.3 13.3 1.5-3.2a1.9 1.9 0 0 1 3-.6l4.4 4.4.7-.7Z"/></svg>';

export default {
	id: 'astro:xray',
	name: 'Inspect',
	icon: icon,
	init(canvas, eventTarget) {
		let islandsOverlays: { highlightElement: DevToolbarHighlight; island: HTMLElement }[] = [];

		addIslandsOverlay();

		document.addEventListener('astro:after-swap', addIslandsOverlay);
		document.addEventListener('astro:page-load', refreshIslandsOverlayPositions);

		function onPageClick(event: MouseEvent) {
			const target = event.target as Element | null;
			if (!target) return;
			if (!target.closest) return;
			if (target.closest('astro-dev-toolbar')) return;
			event.preventDefault();
			event.stopPropagation();
			eventTarget.dispatchEvent(
				new CustomEvent('toggle-app', {
					detail: {
						state: false,
					},
				})
			);
		}

		eventTarget.addEventListener('app-toggled', (event: any) => {
			if (event.detail.state === true) {
				document.addEventListener('click', onPageClick, true);
			} else {
				document.removeEventListener('click', onPageClick, true);
			}
		});

		function addIslandsOverlay() {
			islandsOverlays.forEach(({ highlightElement }) => {
				highlightElement.remove();
			});
			islandsOverlays = [];

			const islands = document.querySelectorAll<HTMLElement>('astro-island');

			if (islands.length === 0) {
				const window = createWindowElement(
					`<style>
						header {
							display: flex;
						}

						h1 {
							display: flex;
							align-items: center;
							gap: 8px;
							font-weight: 600;
							color: #fff;
							margin: 0;
							font-size: 22px;
						}

						astro-dev-toolbar-icon {
							width: 1em;
						   height: 1em;
						   padding: 8px;
							display: block;
							background: #5f9ea0;
							border-radius: 9999px;
						}
					</style>
					<header>
						<h1><astro-dev-toolbar-icon icon="lightbulb"></astro-dev-toolbar-icon>No islands detected.</h1>
					</header>
					<p>
						It looks like there are no interactive component islands on this page. Did you forget to add a client directive to your interactive UI component?
					</p>
					`
				);

				canvas.append(window);
				return;
			}

			islands.forEach((island) => {
				const computedStyle = window.getComputedStyle(island);
				const islandElement = (island.children[0] as HTMLElement) || island;

				// If the island is hidden, don't show an overlay on it
				// TODO: For `client:only` islands, it might not have finished loading yet, so we should wait for that
				if (islandElement.offsetParent === null || computedStyle.display === 'none') {
					return;
				}

				const rect = islandElement.getBoundingClientRect();
				const highlight = createHighlight(rect);
				const tooltip = buildIslandTooltip(island);

				// Set the highlight/tooltip as being fixed position the highlighted element
				// is fixed. We do this so that we don't mistakenly take scroll position
				// into account when setting the tooltip/highlight positioning.
				//
				// We only do this once due to how expensive computed styles are to calculate,
				// and are unlikely to change. If that turns out to be wrong, reconsider this.
				const { isFixed } = getElementsPositionInDocument(islandElement);
				if (isFixed) {
					tooltip.style.position = highlight.style.position = 'fixed';
				}

				attachTooltipToHighlight(highlight, tooltip, islandElement);
				canvas.append(highlight);
				islandsOverlays.push({ highlightElement: highlight, island: islandElement });
			});

			(['scroll', 'resize'] as const).forEach((event) => {
				window.addEventListener(event, refreshIslandsOverlayPositions);
			});
		}

		function refreshIslandsOverlayPositions() {
			islandsOverlays.forEach(({ highlightElement, island: islandElement }) => {
				const rect = islandElement.getBoundingClientRect();
				positionHighlight(highlightElement, rect);
			});
		}

		function buildIslandTooltip(island: HTMLElement) {
			const tooltip = document.createElement('astro-dev-toolbar-tooltip');
			tooltip.sections = [];

			const islandProps = island.getAttribute('props')
				? JSON.parse(island.getAttribute('props')!)
				: {};
			const islandClientDirective = island.getAttribute('client');

			// Add the component client's directive if we have one
			if (islandClientDirective) {
				tooltip.sections.push({
					title: 'Client directive',
					inlineTitle: `<code>client:${islandClientDirective}</code>`,
				});
			}

			// Display the props if we have any
			// Ignore the "data-astro-cid-XXXXXX" prop (internal)
			const islandPropsEntries = Object.entries(islandProps).filter(
				(prop: any) => !prop[0].startsWith('data-astro-cid-')
			);
			if (islandPropsEntries.length > 0) {
				tooltip.sections.push({
					title: 'Props',
					content: `<pre><code>${JSON.stringify(
						Object.fromEntries(islandPropsEntries.map((prop: any) => [prop[0], prop[1][1]])),
						undefined,
						2
					)}</code></pre>`,
				});
			}

			// Add a click action to go to the file
			const islandComponentPath = island.getAttribute('component-url');
			if (islandComponentPath) {
				tooltip.sections.push({
					content: islandComponentPath,
					clickDescription: 'Click to go to file',
					async clickAction() {
						// NOTE: The path here has to be absolute and without any errors (no double slashes etc)
						// or Vite will silently fail to open the file. Quite annoying.
						await fetch(
							'/__open-in-editor?file=' +
								encodeURIComponent(
									(window as DevToolbarMetadata).__astro_dev_toolbar__.root +
										islandComponentPath.slice(1)
								)
						);
					},
				});
			}

			return tooltip;
		}
	},
} satisfies DevToolbarApp;
