import {
  createDocument,
  getDocument,
  getDocuments,
  transformDocument,
} from "@lib/documents.ts";
import { Root } from "https://esm.sh/remark-frontmatter@4.0.1";
import { GenericResource } from "@lib/types.ts";
import { parseRating } from "@lib/helpers.ts";
import { isLocalImage } from "@lib/string.ts";
import { SILVERBULLET_SERVER } from "@lib/env.ts";
import { imageTable } from "@lib/db/schema.ts";
import { db } from "@lib/db/sqlite.ts";
import { eq } from "drizzle-orm/sql";
import { createCache } from "@lib/cache.ts";

export async function addThumbnailToResource<T extends GenericResource>(
  res: T,
): Promise<T> {
  if (!res?.meta?.image) return res;

  const imageUrl = isLocalImage(res.meta.image)
    ? `${SILVERBULLET_SERVER}/${res.meta.image}`
    : res.meta.image;

  const image = await db.select().from(imageTable)
    .where(eq(imageTable.url, imageUrl))
    .limit(1)
    .then((images) => images[0]);

  if (image) {
    return {
      ...res,
      meta: {
        ...res.meta,
        average: image.average,
        thumbnail: image.blurhash,
      },
    };
  }
  return res;
}

type SortType = "rating" | "date" | "name" | "author";

function sortFunction<T extends GenericResource>(sortType: SortType) {
  return (a: T, b: T) => {
    switch (sortType) {
      case "rating":
        return parseRating(a.meta?.rating || 0) >
            parseRating(b.meta?.rating || 0)
          ? -1
          : 1;
      case "date":
        return (a.meta?.date || 0) > (b.meta?.date || 0) ? -1 : 1;
      case "name":
        return a.name.localeCompare(b.name);
      case "author":
        return a.meta?.author?.localeCompare(b.meta?.author || "") || 0;
      default:
        return 0;
    }
  };
}

export function createCrud<T extends GenericResource>(
  { prefix, parse, render, hasThumbnails = false }: {
    prefix: string;
    hasThumbnails?: boolean;
    render?: (doc: T) => string;
    parse: (doc: string, id: string) => T;
  },
) {
  const cache = createCache<T>(`crud/${prefix}`, { expires: 60 * 1000 });

  function pathFromId(id: string) {
    return `${prefix}${id.replaceAll(":", "")}.md`;
  }

  async function read(id: string) {
    const path = pathFromId(id);

    if (cache.has(path)) {
      return cache.get(path);
    }

    const content = await getDocument(path);
    if (!content) {
      return;
    }

    const parsed = parse(content, id);

    if (hasThumbnails) {
      return addThumbnailToResource(parsed);
    }
    const doc = { ...parsed, content };
    cache.set(path, doc, { expires: 10 * 1000 });

    return doc;
  }
  function create(id: string, content: string | ArrayBuffer | T) {
    const path = pathFromId(id);
    cache.set("all", undefined);
    if (
      typeof content === "string" || content instanceof ArrayBuffer
    ) {
      return createDocument(path, content);
    }

    if (render) {
      const rendered = render(content);
      cache.set(path, content);
      return createDocument(path, rendered);
    }

    throw new Error("No renderer defined for " + prefix + " CRUD");
  }

  async function update(id: string, updater: (r: Root) => Root) {
    const path = pathFromId(id);
    const content = await getDocument(path);
    if (!content) {
      return;
    }
    const newDoc = transformDocument(content, updater);
    cache.set("all", undefined);
    await createDocument(path, newDoc);
  }

  async function readAll({ sort = "rating" }: { sort?: SortType } = {}) {
    if (cache.has("all")) {
      return cache.get("all") as unknown as T[];
    }
    const allDocuments = await getDocuments();
    const parsed = (await Promise.all(
      allDocuments.filter((d) => {
        return d.name.startsWith(prefix) &&
          d.contentType === "text/markdown" &&
          !d.name.endsWith("index.md");
      }).map((doc) => {
        const id = doc.name.replace(prefix, "").replace(/\.md$/, "");
        return read(id);
      }),
    )).sort(sortFunction<T>(sort)).filter((v) => !!v);

    cache.set("all", parsed);
    return parsed;
  }

  return {
    read,
    readAll,
    create,
    update,
  };
}
