import {
  Options as ToJsxRuntimeOptions,
  toJsxRuntime,
} from "hast-util-to-jsx-runtime";
import type { Heading, Node, ThematicBreak } from "mdast";

import { fromMarkdown } from "mdast-util-from-markdown";
import { toHast } from "mdast-util-to-hast";
import { toString } from "mdast-util-to-string";
import Link from "next/link";
import * as runtime from "react/jsx-runtime";
import { visit } from "unist-util-visit";

const toJsxRuntimeOptions: ToJsxRuntimeOptions = {
  ...runtime,
  components: {
    a({ href, ...props }: any) {
      if (href.startsWith("/")) {
        return <Link href={href} {...props} />;
      }
      return <a href={href} {...props} />;
    },
  },
};

interface RemarkParseResult {
  heading: Heading;
  body: Node[];
  features: { title: Heading; content: Node[] }[];
  footer?: Node[];
}

function parse(markdown: string): RemarkParseResult {
  const tree = fromMarkdown(markdown);

  visit(
    tree,
    (node): node is import("mdast").Link => node.type === "link",
    (link) => {
      link.data ||= {};
      link.data.hProperties ||= {};
      if (/^https?:\/\//.test(link.url)) {
        link.data.hProperties.target = "_blank";
      }
    }
  );

  const result: Partial<RemarkParseResult> = {};

  for (const child of tree.children) {
    if (!result.heading) {
      if (isHeading(child, 1)) {
        result.heading = child;
        result.body = [];
      }
      continue;
    }

    if (!result.features) {
      if (!isHeading(child, 2)) {
        result.body!.push(child);
      } else {
        result.features = [
          {
            title: child,
            content: [],
          },
        ];
      }
      continue;
    }

    if (result.footer) {
      result.footer.push(child);
      continue;
    } else if (isThematicBreak(child)) {
      result.footer = [];
      continue;
    }

    if (isHeading(child, 2)) {
      result.features.push({
        title: child,
        content: [],
      });
    } else {
      result.features[result.features.length - 1].content.push(child);
    }
  }

  return result as any;
}

function compile(result: RemarkParseResult) {
  return {
    heading: toHast(result.heading),
    body: fragmentToHast(result.body),
    features: result.features.map((feature) => ({
      title: toHast(feature.title),
      content: fragmentToHast(feature.content),
    })),
    footer: result.footer ? fragmentToHast(result.footer) : undefined,
  };
}

function render(compiled: ReturnType<typeof compile>) {
  return {
    heading: toJsxRuntime(compiled.heading, toJsxRuntimeOptions),
    body: toJsxRuntime(
      { type: "root", children: compiled.body },
      toJsxRuntimeOptions
    ),
    features: compiled.features.map((feature) => ({
      title: toJsxRuntime(feature.title, toJsxRuntimeOptions),
      content: toJsxRuntime(
        { type: "root", children: feature.content },
        toJsxRuntimeOptions
      ),
    })),
    footer: compiled.footer
      ? toJsxRuntime(
          { type: "root", children: compiled.footer },
          toJsxRuntimeOptions
        )
      : undefined,
  };
}

function fragmentToHast(fragment: Node[]) {
  return (
    toHast({ type: "root", children: fragment as any }) as import("hast").Root
  ).children;
}

export function renderFeatures(markdown: string) {
  const parsed = parse(markdown);

  const title = toString(parsed.heading);

  return {
    result: render(compile(parsed)),
    title,
  };
}

function isHeading(node: Node, level: number): node is Heading {
  return node.type === "heading" && (node as Heading).depth === level;
}

function isThematicBreak(node: Node): node is ThematicBreak {
  return node.type === "thematicBreak";
}
