function assert(condition: any, message?: string): asserts condition {
  if (!condition) {
    throw new Error(message || "Assertion failed");
  }
}

export const customRules = defaultRules.clone();

/** A question mention, formatted like `#112233445566` */
customRules.insertBefore("em", {
  name: "mentionQuestion",
  match: inlineRegex(/^#([0-9]{10})(?:[0-9]{2})?/),
  parse(capture) {
    return {
      id: capture[1],
    };
  },
});

/** A question mention, formatted like `@pheonix-write` */
customRules.insertBefore("em", {
  name: "mentionArtifact",
  match: inlineRegex(/^@([a-z0-9-]+)/i),
  parse(capture) {
    return {
      id: capture[1],
    };
  },
});

/** The censor bar used for past deadnaming: #name# and #username#  */
customRules.insertBefore("em", {
  name: "deadname",
  match: inlineRegex(/^#(name|username)#/i),
  parse(capture) {
    return {
      kind: capture[1],
    };
  },
});

/** html block. allows arbitrary html, but rn only as an answer. formatted like `@html content here` */
customRules.insertBefore("paragraph", {
  name: "html",
  match: blockRegex(/^@html ((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/),
  parse(capture) {
    return {
      data: capture[1],
    };
  },
});

/** for server-rendered search, mark can highlight the seen word */
customRules.insertBefore("paragraph", {
  name: "mark",
  match: inlineRegex(/^<!!mark!!>(.*?)<!!mark!!>/),
  parse: parseCaptureInline,
});

/** Adjust autolinking to use my own components if possible */
customRules.insertBefore("url", {
  name: "autolink_mention",
  match: inlineRegex(
    /^https?:\/\/(?:paper(?:dave|clover)\.net|localhost:\d+)\/([^\s<]+[^<.,:;"')\]\s])/,
  ),
  parse(capture) {
    const qaMatch = capture[1].match(/^q\+a\/([0-9]{10}(?:[0-9]{2})?)/);
    if (qaMatch) {
      return {
        type: "mentionQuestion",
        id: qaMatch[1],
      };
    }

    return {
      type: "link",
      content: [
        {
          type: "text",
          content: capture[1],
        },
      ],
      target: capture[1],
      title: undefined,
    };
  },
});

export const questionRules = customRules.clone();

questionRules.remove("heading");

/** Input paragraphs. any paragraph prefixed with `i:` */
questionRules.insertBefore("paragraph", {
  name: "question",
  match: blockRegex(/^(?:q: [^\n]+(?:\n|$))+\s*/),
  parse(capture, parse, state) {
    const lines = capture[0]
      .trim()
      .split("\n")
      .map((line) => line.trim())
      .filter((x) => x.length > 0)
      .map((x) => {
        assert(x.startsWith("q: "));
        return x.slice(3);
      });
    const content = lines.map((line, i) => {
      const parsed = parseInline(parse, line, state);
      if (i < lines.length - 1) {
        parsed.push({ type: "br" });
      }
      return parsed;
    }).flat();
    return {
      content,
    };
  },
});

const cloverParser = createParser(questionRules);

const basicNodeTagNames: Record<string, string> = {
  mark: "mark",
  question: "q-",
  paragraph: "p",
  hr: "hr",
  blockQuote: "blockquote",
  em: "em",
  strong: "strong",
  u: "u",
  del: "del",
  inlineCode: "code",
  br: "br",
};

function BasicNode(node: ASTNode, children: any) {
  const E = basicNodeTagNames[node.type];
  return <E>{children}</E>;
}

function ListRenderer(node: ASTNode, children: any[]) {
  const T = node.ordered ? "ol" : "ul";
  return <T>{children.map((child) => <li>{child}</li>)}</T>;
}

function MarkdownLink(node: ASTNode, children: any[]) {
  return <a href={node.target}>{children}</a>;
}

function Deadname(node: ASTNode) {
  return <span class="dead">old {node.kind}</span>;
}

function QuestionRef(node: ASTNode) {
  if (node.id.length === 12) node.id = node.id.slice(0, 10);
  const timestamp = questionIdToTimestamp(node.id);
  if (!timestamp) {
    console.error(`Invalid question ref: ${node.id}`);
    return <span class="question-ref">[Invalid Question Ref: #{node.id}]</span>;
  }
  return (
    <a class="custom question-ref" href={`/q+a/${node.id}`}>
      {formatQuestionTimestamp(timestamp)}
    </a>
  );
}

function ArtifactRef(node: ASTNode) {
  const artifact = artifactMap[node.id];
  if (!artifact) {
    return <span class="artifact">[Invalid Artifact Ref: @{node.id}]</span>;
  }
  const [title, url, type] = artifact;
  if (!url) {
    return <span>{title}</span>;
  }
  return <a class={`custom artifact artifact-${type}`} href={url}>{title}</a>;
}

const br = <br />;
const ssrFunctions: any = {
  // Default Renders
  // heading: HeadingRenderer,
  hr: BasicNode,
  mark: BasicNode,
  blockQuote: BasicNode,
  // codeBlock: MarkdownCodeBlock,
  list: ListRenderer,
  // table: TableRenderer,
  paragraph: BasicNode,
  // tableSeparator: ???,
  link: MarkdownLink,
  // image: MarkdownImage,
  em: BasicNode,
  strong: BasicNode,
  u: BasicNode,
  del: BasicNode,
  inlineCode: BasicNode,
  br: () => br,
  newline: () => br,
  deadname: Deadname,

  // Custom Elements
  question: BasicNode,
  html: (node: any) => ssr.html(node.data),
  mentionQuestion: QuestionRef,
  mentionArtifact: ArtifactRef,
};

function renderNode(node: string | ASTNode | ASTNode[]): any {
  if (typeof node === "string") {
    return node;
  } else if (Array.isArray(node)) {
    return node.map((item) => renderNode(item));
  } else if (node.type === "text") {
    return node.content;
  } else {
    const render = ssrFunctions[node.type];
    if (!render) {
      throw new Error(`No renderer for node type "${node.type}"`);
    }
    const content = node.content as any;
    return render(node, content ? renderNode(content) : undefined);
  }
}

export namespace CloverMarkdown {
  export interface Props {
    text: string;
    inline?: boolean;
  }
}

export function CloverMarkdown(props: CloverMarkdown.Props) {
  const ast = cloverParser(props.text, { inline: !!props.inline });
  return renderNode(ast);
}

import { artifactMap } from "./artifacts.ts";
import { formatQuestionTimestamp, questionIdToTimestamp } from "./format.ts";
import {
  type ASTNode,
  blockRegex,
  createParser,
  defaultRules,
  inlineRegex,
  parseCaptureInline,
  parseInline,
} from "./simple-markdown.ts";
import * as ssr from "#ssr";
