import {
  type DocumentChild,
  getTextOfChild,
  getTextOfRange,
  parseDocument,
} from "@lib/documents.ts";
import { parse } from "yaml";
import { parseIngredient } from "https://esm.sh/parse-ingredient@1.0.1";
import { createCrud } from "@lib/crud.ts";
import { extractHashTags } from "@lib/string.ts";

export type IngredientGroup = {
  name: string;
  ingredients: Ingredient[];
};

export type Ingredient = {
  type: string;
  unit?: string;
  amount?: string;
};

export type Ingredients = (Ingredient | IngredientGroup)[];

export type Recipe = {
  type: "recipe";
  id: string;
  name: string;
  description?: string;
  ingredients: Ingredients;
  preparation?: string;
  tags: string[];
  meta?: {
    time?: string;
    link?: string;
    image?: string;
    rating?: number;
    portion?: number;
    author?: string;
    average?: string;
    thumbnail?: string;
  };
};

function parseIngredientItem(listItem: DocumentChild): Ingredient | undefined {
  if (listItem.type === "listItem") {
    const children: DocumentChild[] = listItem.children[0]?.children ||
      listItem.children;

    const text = children.map((c) => getTextOfChild(c)).join(" ").trim();

    const ing = parseIngredient(text, {
      additionalUOMs: {
        tableSpoon: {
          short: "EL",
          plural: "Table Spoons",
          alternates: ["el", "EL", "Tbsp", "tbsp"],
        },
        teaSpoon: {
          short: "TL",
          plural: "Tea Spoon",
          alternates: ["tl", "TL", "Tsp", "tsp", "teaspoon"],
        },
        litre: {
          short: "L",
          plural: "liters",
          alternates: ["L", "l"],
        },
        paket: {
          short: "Paket",
          plural: "Pakets",
          alternates: ["Paket", "paket"],
        },
      },
    });

    return {
      type: ing[0].description,
      unit: ing[0].unitOfMeasure,
      amount: ing[0].quantity,
    };
  }
  return;
}

const isIngredient = (item: Ingredient | undefined): item is Ingredient => {
  return !!item;
};

function parseIngredientsList(list: DocumentChild): Ingredient[] {
  if (list.type === "list" && "children" in list) {
    return list.children.map((listItem) => {
      return parseIngredientItem(listItem);
    }).filter(isIngredient);
  }
  return [];
}

function parseIngredients(children: DocumentChild[]): Recipe["ingredients"] {
  const ingredients: (Ingredient | IngredientGroup)[] = [];
  if (!children) return [];
  let skip = false;
  for (let i = 0; i < children.length; i++) {
    if (skip) {
      skip = false;
      continue;
    }
    const child = children[i];

    if (child.type === "paragraph") {
      const nextChild = children[i + 1];

      if (nextChild.type !== "list") continue;

      ingredients.push({
        name: getTextOfChild(child) || "",
        ingredients: parseIngredientsList(nextChild),
      });
      skip = true;
      continue;
    }

    if (child.type === "list") {
      ingredients.push(...parseIngredientsList(child));
    }
  }

  return ingredients;
}

export function parseRecipe(original: string, id: string): Recipe {
  const doc = parseDocument(original);

  let name = "";
  let meta: Recipe["meta"] = {};

  const groups: DocumentChild[][] = [];
  let group: DocumentChild[] = [];
  for (const child of doc.children) {
    if (child.type === "yaml") {
      meta = parse(child.value) as Recipe["meta"];
      continue;
    }
    if (
      child.type === "heading" && child.depth === 1 && !name &&
      child.children.length === 1 && child.children[0].type === "text"
    ) {
      name = child.children[0].value;
      continue;
    }
    if (child.type === "thematicBreak") {
      groups.push(group);
      group = [];
      continue;
    }
    group.push(child);
  }

  if (group.length) {
    groups.push(group);
  }

  let description = getTextOfRange(groups[0], original);

  const ingredients = parseIngredients(groups[1]);

  const preparation = getTextOfRange(groups[2], original);

  const tags = extractHashTags(description || "");
  if (description) {
    for (const tag of tags) {
      description = description.replace("#" + tag, "");
    }
  }

  return {
    type: "recipe",
    id,
    meta,
    name,
    tags,
    description,
    ingredients,
    preparation,
  };
}

const crud = createCrud<Recipe>({
  prefix: `Recipes/`,
  parse: parseRecipe,
  hasThumbnails: true,
});

export const getAllRecipes = crud.readAll;
export const getRecipe = crud.read;
export const updateRecipe = crud.update;
export const createRecipe = crud.create;
