import React from "react";
import ReactDOM from "react-dom/client";
import browser from "webextension-polyfill";
import type { VNode } from "./lib/extract";
import { cssPropertiesDefault } from "./lib/CSSPropertiesDefaultValue";

console.log("Hello from sidebar!");



function getUniqueSelector(el: Element) {
    if (!(el instanceof Element))
        throw new Error("Invalid argument");
    var path = [];
    while (el.nodeType === Node.ELEMENT_NODE) {
        let selector = el.nodeName.toLowerCase();
        const parentElement = el.parentElement;
        if (el.id) {
            selector += '#' + el.id;
            path.unshift(selector);
            break;
        }
        if (!parentElement) {
            break;
        }
        let nth = 1;
        let sib = el.previousElementSibling;
        while (sib) {
            nth++;
            sib = sib.previousElementSibling;
        }
        selector += ":nth-child(" + nth + ")";

        path.unshift(selector);
        el = parentElement;
    }
    return path.join(" > ");
}
const code = `
(() => {
    let getUniqueSelector = ${getUniqueSelector.toString()};
    return getUniqueSelector($0);
})()
`

function useSelectedElement() {
    const [element, setElement] = React.useState<{
        vnode: VNode,
        inheritedStyle: { [key: string]: string }
    } | null>();
    React.useEffect(() => {
        const handler = async () => {
            const id = Math.floor(Math.random() * 1000000);
            // get selector of the selected element
            console.log("evaluating code");
            const [selector, isException] = await browser.devtools.inspectedWindow.eval(code);
            if (isException) {
                console.error("Error evaluating code", isException);
                return;
            }
            console.log(selector);

            const tab = await browser.tabs.query({ active: true, currentWindow: true });
            const tabId = browser.devtools.inspectedWindow.tabId;
            console.log("sending message", tabId);

            // post message to content script to get vnode of the selected element
            const res = await browser.tabs.sendMessage(tabId, { type: "cloneWithStyle", elementSelector: selector, id });
            console.log("got response", res);
            if (!res) {
                console.error("No response");
                return;
            }
            if (res.type !== "cloneWithStyleResult") {
                console.error("Unexpected response", res);
                return;
            }
            console.log("got response");
            setElement({
                vnode: res.vnode,
                inheritedStyle: res.inheritedStyle
            });
        };
        if (!element) {
            handler();
        }

        browser.devtools.panels.elements.onSelectionChanged.addListener(handler);
        return () => {
            browser.devtools.panels.elements.onSelectionChanged.removeListener(handler);
        };
    }, []);
    return element;
}

function useGetComputedStyle() {
    const [styles, setStyles] = React.useState<{ [key: string]: string } | null>(null);
    React.useEffect(() => {
        const handler = async () => {
            const [styles, isException] = await browser.devtools.inspectedWindow.eval(`
            (() => {
                const element = $0;
                const style = getComputedStyle(element);
                const styles = {};
                for (let i = 0; i < style.length; i++) {
                    const property = style.item(i);
                    styles[property] = style.getPropertyValue(property);
                }
                
                return styles;
            })()
            `);
            if (isException) {
                console.error("Error evaluating code", isException);
                return;
            }
            let newStyles = {} as { [key: string]: string };
            for (const key in styles) {
                if (key in cssPropertiesDefault) {
                    const value = cssPropertiesDefault[key as keyof typeof cssPropertiesDefault];
                    if (styles[key] != value) {
                        newStyles[key] = value;
                    }
                }
                else {
                    newStyles[key] = styles[key];
                }
            }
            setStyles(newStyles);
        };
        if (!styles) {
            handler();
        }

        browser.devtools.panels.elements.onSelectionChanged.addListener(handler);
        return () => {
            browser.devtools.panels.elements.onSelectionChanged.removeListener(handler);
        };
    }, []);
    return styles;
}

function kebabToCamel(str: string) {
    return str.replace(/-./g, (match) => match[1].toUpperCase());
}

function htmlStyleToReactStyle(style: { [key: string]: string }) {
    return Object.fromEntries(Object.entries(style).map(([key, value]) => [kebabToCamel(key), value]));
}

const HTMLComponentTable: { [key: string]: keyof JSX.IntrinsicElements } = {
    "a": "a",
    "abbr": "abbr",
    "address": "address",
    "area": "area",
    "article": "article",
    "aside": "aside",
    // "audio": "audio",
    "b": "b",
    "base": "base",
    "bdi": "bdi",
    "bdo": "bdo",
    "big": "big",
    "blockquote": "blockquote",
    "body": "body",
    "br": "br",
    "button": "button",
    "canvas": "canvas",
    "caption": "caption",
    "center": "center",
    "cite": "cite",
    "code": "code",
    "col": "col",
    "colgroup": "colgroup",
    "data": "data",
    "datalist": "datalist",
    "dd": "dd",
    "del": "del",
    "details": "details",
    "dfn": "dfn",
    "dialog": "dialog",
    "div": "div",
    "dl": "dl",
    "dt": "dt",
    "em": "em",
    "embed": "embed",
    "fieldset": "fieldset",
    "figcaption": "figcaption",
    "figure": "figure",
    "footer": "footer",
    "form": "form",
    "h1": "h1",
    "h2": "h2",
    "h3": "h3",
    "h4": "h4",
    "h5": "h5",
    "h6": "h6",
    // "head": "head",
    "header": "header",
    "hgroup": "hgroup",
    "hr": "hr",
    // "html": "html",
    "i": "i",
    // "iframe": "iframe",
    // "img": "img",
    "input": "input",
    "ins": "ins",
    "kbd": "kbd",
    "keygen": "keygen",
    "label": "label",
    "legend": "legend",
    "li": "li",
    "link": "link",
    "main": "main",
    "map": "map",
    "mark": "mark",
    "menu": "menu",
    "menuitem": "menuitem",
    // "meta": "meta",
    "meter": "meter",
    "nav": "nav",
    "noindex": "noindex",
    "noscript": "noscript",
    "object": "object",
    "ol": "ol",
    "optgroup": "optgroup",
    "option": "option",
    "output": "output",
    "p": "p",
    "param": "param",
    "picture": "picture",
    "pre": "pre",
    "progress": "progress",
    "q": "q",
    "rp": "rp",
    "rt": "rt",
    "ruby": "ruby",
    "s": "s",
    "samp": "samp",
    "search": "search",
    "slot": "slot",
    "script": "script",
    "section": "section",
    "select": "select",
    "small": "small",
    "source": "source",
    "span": "span",
    "strong": "strong",
    "style": "style",
    "sub": "sub",
    "summary": "summary",
    "sup": "sup",
    "table": "table",
    "template": "template",
    "tbody": "tbody",
    "td": "td",
    "textarea": "textarea",
    "tfoot": "tfoot",
    "th": "th",
    "thead": "thead",
    "time": "time",
    "title": "title",
    "tr": "tr",
    "track": "track",
    "u": "u",
    "ul": "ul",
    "var": "var",
    // "video": "video",
    "wbr": "wbr",
    "webview": "webview",
}

function VnodeToReact({ vnode }: { vnode: VNode }): React.ReactNode {
    if (vnode.type === "text") {
        return vnode.text;
    }
    else if (vnode.type === "element") {
        // TODO: support svg
        const Tag = HTMLComponentTable[vnode.tagName] ?? "div";
        const style = vnode.style;
        const children = vnode.children;
        return <Tag style={htmlStyleToReactStyle(style)}>
            {children.map((child, i) => <VnodeToReact key={i} vnode={child} />)}
        </Tag>
    }
}

function escapseAttrValue(value: string) {
    return value.replaceAll('"', '&quot;')
}

function VnodeToHTML({ vnode }: { vnode: VNode }): string {
    if (vnode.type === "text") {
        return vnode.text;
    }
    else if (vnode.type === "element") {
        const Tag = vnode.tagName;
        const style = vnode.style;
        const children = vnode.children;
        return `<${Tag} style="${Object.entries(style).map(([key, value]) => `${key}: ${escapseAttrValue(value)}`).join(";")}">
            ${children.map((child, i) => VnodeToHTML({ vnode: child })).join("")}
        </${Tag}>`
    }
    return "";
}

function inheritStyleToStyleText(inheritedStyle: { [key: string]: string }) {
    return Object.entries(inheritedStyle).map(([key, value]) => `${key}: ${value}`).join(";\n");
}

function Content() {
    const data = useSelectedElement();
    const inheritedStyleText = data ? inheritStyleToStyleText(data.inheritedStyle) : null;
    const html = data ? VnodeToHTML({ vnode: data?.vnode }) : "";
    return <div>
        <h1>Vnode</h1>
        <code
            lang="json"
            style={{
                width: "100%",
                height: "4rem",
                overflow: "auto",
                position: "relative"
            }}
        >
            {inheritedStyleText}
        </code>
        <div>
            <code lang="html"
                style={{
                    width: "100%",
                    height: "20rem",
                    overflow: "auto",
                    position: "relative"
                }}
            >
                {html}
            </code>
            {data &&
                <div style={data.inheritedStyle}>
                    <VnodeToReact vnode={data.vnode} />
                </div>
            }
        </div>
    </div>;
}

ReactDOM.createRoot(document.querySelector("#app")!).render(
    <React.StrictMode>
        <Content />
    </React.StrictMode>
);