import { unified } from "https://esm.sh/unified@10.1.2";
import { render } from "gfm";
import "https://esm.sh/prismjs@1.29.0/components/prism-typescript?no-check";
import "https://esm.sh/prismjs@1.29.0/components/prism-bash?no-check";
import "https://esm.sh/prismjs@1.29.0/components/prism-rust?no-check";
import remarkParse from "https://esm.sh/remark-parse@10.0.2";
import remarkStringify from "https://esm.sh/remark-stringify@10.0.3";
import remarkFrontmatter, {
  Root,
} from "https://esm.sh/remark-frontmatter@4.0.1";
import { SILVERBULLET_SERVER } from "@lib/env.ts";
import { fixRenderedMarkdown } from "@lib/helpers.ts";
import { createLogger } from "@lib/log.ts";
import { db } from "@lib/sqlite/sqlite.ts";
import { documentTable } from "@lib/sqlite/schema.ts";
import { eq } from "drizzle-orm/sql";

export type Document = {
  name: string;
  lastModified: number;
  contentType: string;
  content: string | null;
  size: number;
  perm: string;
};

const log = createLogger("documents");

export async function getDocuments(): Promise<Document[]> {
  let documents = await db.select().from(documentTable).all();
  if (documents.length) return documents;

  const headers = new Headers();
  headers.append("Accept", "application/json");
  headers.append("X-Sync-Mode", "true");
  log.debug("fetching all documents");
  const response = await fetch(`${SILVERBULLET_SERVER}/index.json`, {
    headers: headers,
  });

  documents = await response.json();
  await db.delete(documentTable);
  await db.insert(documentTable).values(documents);

  return documents;
}

export function createDocument(
  name: string,
  content: string | ArrayBuffer,
  mediaType?: string,
) {
  const headers = new Headers();

  if (mediaType) {
    headers.append("Content-Type", mediaType);
  }

  log.info("creating document", { name });

  return fetch(SILVERBULLET_SERVER + "/" + name, {
    body: content,
    method: "PUT",
    headers,
  });
}

export async function getDocument(name: string): Promise<string> {
  const documents = await db.select().from(documentTable).where(
    eq(documentTable.name, name),
  ).limit(1);
  if (documents[0]?.content) return documents[0].content;

  log.debug("fetching document", { name });
  const headers = new Headers();
  headers.append("X-Sync-Mode", "true");
  const response = await fetch(SILVERBULLET_SERVER + "/" + name, { headers });
  const text = await response.text();

  await db.update(documentTable).set({
    content: text,
  }).where(eq(documentTable.name, name));

  return text;
}

export function transformDocument(input: string, cb: (r: Root) => Root) {
  const out = unified()
    .use(remarkParse)
    .use(remarkFrontmatter, ["yaml"])
    .use(() => (tree) => {
      return cb(tree);
    })
    .use(remarkStringify)
    .processSync(input);

  return fixRenderedMarkdown(String(out));
}

export function parseDocument(doc: string) {
  return unified()
    .use(remarkParse)
    .use(remarkFrontmatter, ["yaml", "toml"])
    .parse(doc);
}

export function renderMarkdown(doc: string) {
  return render(doc, {
    allowMath: true,
  });
}

export type ParsedDocument = ReturnType<typeof parseDocument>;
export type DocumentChild = ParsedDocument["children"][number];

export function findRangeOfChildren(children: DocumentChild[]) {
  const firstChild = children[0];
  const lastChild = children.length > 1
    ? children[children.length - 1]
    : firstChild;

  const start = firstChild.position?.start.offset;
  const end = lastChild.position?.end.offset;

  if (typeof start !== "number" || typeof end !== "number") return;

  return [start, end];
}

export function getTextOfRange(children: DocumentChild[], text: string) {
  if (!children || children.length === 0) {
    return;
  }

  const range = findRangeOfChildren(children);
  if (!range) return;
  return text.substring(range[0], range[1]);
}

export function getTextOfChild(child: DocumentChild): string | undefined {
  if ("value" in child) return child.value;
  if ("children" in child) {
    return getTextOfChild(child.children[0]);
  }
  return;
}
