"use client";

import { Accordion, AccordionContent } from "@/lib/accordion";
import { Poppable } from "@/lib/poppables/components/poppable";
import { createElements } from "@/lib/tcmd";
import Link from "next/link";
import React, { FC, Fragment, ReactNode, use, useMemo } from "react";

import { sanitize } from "isomorphic-dompurify";

export const TTCMD: FC<{ body: Promise<string> }> = ({ body }) => {
  const text = use(body);
  const elements = useMemo(() => createElements(text), [text]);
  return (
    // <div className="grid grid-cols-2">
    //   <pre>{JSON.stringify(elements,null,2)}</pre>
    // </div>
    <div className="flex flex-col gap-6 my-6">
      {elements.map((e, i) => <Fragment key={e.uuid}>{renderBlock(e)}
      </Fragment>)}
    </div>
  );
};

const renderBlock = (block: BlockChildren): ReactNode => {
  switch (block.type) {
    case "block":
      return block.children.map((e, i) => (
        <Fragment key={e.uuid}>{renderBlock(e)}</Fragment>
      ));
    case "grid":
      return (
        <div
          style={{
            "--grid-cols": block.metadata.columns,
          } as React.CSSProperties}
          className="grid grid-cols-dynamic gap-x-8 gap-y-6 mb-6"
        >
          {block.children.map((c, i) => (
            <div key={c.uuid}>
              {renderBlock(c)}
            </div>
          ))}
        </div>
      );
    case "card":
      return (
        <div className="card">
          {block.children.map((e, i) => (
            <Fragment key={e.uuid}>
              {renderBlock(e)}
            </Fragment>
          ))}
        </div>
      );
    case "accordion":
      return (
        <Accordion
          title={block.metadata.title || "Expand"}
        >
          <AccordionContent>
            {block.children.map((e, i) => (
              <Fragment key={e.uuid}>
                {renderBlock(e)}
              </Fragment>
            ))}
          </AccordionContent>
        </Accordion>
      );
    default:
      return (
        renderParagraph(block as ParagraphToken)
      );
  }
};

const renderParagraph = (p: ParagraphToken) => {
  switch (p.type) {
    case "p":
      return (
        <div className="p">
          {p.content.map((e, i) => (
            <Fragment key={e.uuid}>
              {renderToken(e)}
            </Fragment>
          ))}
        </div>
      );
    case "code":
      return (
        <pre className="whitespace-pre-wrap bg-black/20 p-2 rounded-md">
          {p.content.map((c) => c.line.toString()).join("\n\n")}
        </pre>
      );
    default:
      return (
        <div className="p bg-red-600 text-white">
          Block or paragraph missing implementation: {p.type}
        </div>
      );
  }
};

const renderToken = (t: Token) => {
  switch (t.type) {
    case "h1":
      return (
        <div
          id={t.raw.toLowerCase().replace(/[^a-z\s]/ig, "").trim().replaceAll(
            " ",
            "-",
          )}
          className="font-bold text-2xl p"
        >
          {renderInlineToken(t.line)}
        </div>
      );
    case "h2":
      return (
        <div
          id={t.raw.toLowerCase().replace(/[^a-z\s]/ig, "").trim().replaceAll(
            " ",
            "-",
          )}
          className="font-bold text-xl p"
        >
          {renderInlineToken(t.line)}
        </div>
      );
    case "h3":
      return (
        <div
          id={t.raw.toLowerCase().replace(/[^a-z\s]/ig, "").trim().replaceAll(
            " ",
            "-",
          )}
          className="font-bold text-lg p"
        >
          {renderInlineToken(t.line)}
        </div>
      );
    case "p":
      return (
        <div className="p">
          {t.lines.map((e, i) => (
            <Fragment key={e.uuid}>
              {renderInlineToken(e.line)}
            </Fragment>
          ))}
        </div>
      );
    case "text":
      return (
        <>
          {renderInlineToken(t.line)}
          &nbsp;
        </>
      );
    case "list1":
      return <li className="list-disc ml-6">{renderInlineToken(t.line)}</li>;
    case "list2":
      return <li className="list-disc ml-12">{renderInlineToken(t.line)}</li>;
    default:
      return (
        <div className="text-white bg-red-500 p">
          Missing implementation for tcMD element `{(t as { type: string })
            .type}`
        </div>
      );
  }
};

const renderInlineToken = (l: Line) => {
  if (typeof l === "string") return l;

  return l.map((token) => (
    <Fragment key={token.uuid}>
      {(() => {
        switch (token.type) {
          case "text":
            return <span>{token.content}</span>;
          case "bold":
            return <span className="font-bold">{token.content}</span>;
          case "italic":
            return <span className="italic">{token.content}</span>;
          case "anchor":
            return (
              <Link
                className={token.data.style?.classes ||
                  "dark:text-primary-600 underline dark:no-underline"}
                href={token.data.href}
              >
                {token.content}
              </Link>
            );
          case "image": {
            token.data.src = token.data.src as string;
            if (token.data.src.startsWith("<svg")) {
              return (
                <div
                  dangerouslySetInnerHTML={{
                    __html: sanitize(token.data.src, {
                      USE_PROFILES: { svg: true },
                    }),
                  }}
                >
                </div>
              );
            }
            // eslint-disable-next-line @next/next/no-img-element
            return <img src={token.data.src} alt={token.content} />;
          }
          case "popover":
            return (
              <Poppable
                content={renderInlineToken(token.data.popover)}
                preferredAlign="centered"
                preferredEdge="bottom"
                className="cursor-pointer"
              >
                <span className="border-b-2 border-dotted border-white">
                  {token.content}
                </span>
              </Poppable>
            );
          default:
            return (
              <span className="bg-red-500">
                Inline element not implemented: {token.type}
              </span>
            );
        }
      })()}
    </Fragment>
  ));
};
