import { createSignal } from "solid-js";

import { Handler } from "mdast-util-to-hast";
import { styled } from "styled-system/jsx";
import { Plugin } from "unified";
import { visit } from "unist-util-visit";

const Spoiler = styled("span", {
  base: {
    padding: "0 2px",
    borderRadius: "var(--borderRadius-md)",
  },
  variants: {
    shown: {
      true: {
        color: "var(--md-sys-color-inverse-on-surface)",
        background: "var(--md-sys-color-inverse-surface)",
      },
      false: {
        cursor: "pointer",
        userSelect: "none",
        color: "transparent",
        background: "#151515",

        "> *": {
          opacity: 0,
          pointerEvents: "none",
        },
      },
    },
  },
});

export function RenderSpoiler(props: {
  children: Element;
  disabled?: boolean;
}) {
  const [shown, setShown] = createSignal(false);

  return (
    <Spoiler
      shown={shown()}
      onClick={props.disabled ? undefined : () => setShown(true)}
    >
      {props.children}
    </Spoiler>
  );
}

export const remarkSpoiler: Plugin = () => (tree) => {
  visit(
    tree,
    "paragraph",
    (
      node: {
        children: (
          | { type: "text"; value: string }
          | { type: "paragraph"; children: unknown[] }
          | { type: "spoiler"; children: unknown[] }
        )[];
      },
      _idx,
      _parent,
    ) => {
      // Visitor state
      let searchingForEnd = -1;
      let spoilerContent: object[] = [];

      // Visit all children of paragraphs
      for (let i = 0; i < node.children.length; i++) {
        const child = node.children[i];

        // Find the next text element to start a spoiler from
        if (child.type === "text") {
          const components = child.value.split("||");
          if (components.length === 1) continue; // no spoilers

          // Handle terminating spoiler tag
          if (searchingForEnd !== -1) {
            // Get all preceding elements
            const elements = node.children.splice(
              searchingForEnd,
              i - searchingForEnd,
            );

            // Create a spoiler
            node.children.splice(i, 0, {
              type: "spoiler",
              children: [
                ...spoilerContent,
                ...elements,
                {
                  type: "text",
                  value: components.shift(),
                },
              ],
            });

            // Adjust our current index
            i += elements.length + 1;

            searchingForEnd = -1;
            spoilerContent = [];
          }

          // Replace current child with next component
          child.value = components.shift()!;

          // Check how many spoilers we have to process
          const spillOver = components.length % 2 === 1;
          const innerElements = (components.length - (spillOver ? 1 : 0)) / 2;

          // Convert inner elements into spoilers
          if (innerElements) {
            for (let j = 0; j < innerElements; j++) {
              node.children.splice(
                i + 1,
                0,
                {
                  type: "spoiler",
                  children: [
                    {
                      type: "text",
                      value: components.shift(),
                    },
                  ],
                },
                {
                  type: "text",
                  value: components.shift()!,
                },
              );

              i += 2;
            }
          }

          // Update state if we are looking for the end of a spoiler
          if (spillOver) {
            searchingForEnd = i + 1;
            spoilerContent.push({
              type: "text",
              value: components.pop(),
            });
          }
        }
      }
    },
  );
};

export const spoilerHandler: Handler = (h, node) => {
  return {
    type: "element" as const,
    tagName: "spoiler",
    children: h.all({
      type: "paragraph",
      children: node.children,
    }),
    properties: {},
  };
};
