<template>
  <v-container v-if="recipe">
    <v-container>
      <v-alert dismissible border="left" colored-border type="warning" elevation="2" :icon="$globals.icons.alert">
        <b>Experimental Feature</b>
        <div>
          Mealie can use natural language processing to attempt to parse and create units, and foods for your Recipe
          ingredients. This is experimental and may not work as expected. If you choose to not use the parsed results
          you can select cancel and your changes will not be saved.
        </div>
      </v-alert>

      <BaseCardSectionTitle title="Ingredients Processor">
        To use the ingredient parser, click the "Parse All" button and the process will start. When the processed
        ingredients are available, you can look through the items and verify that they were parsed correctly. The models
        confidence score is displayed on the right of the title item. This is an average of all scores and may not be
        wholely accurate.

        <div class="my-4">
          Alerts will be displayed if a matching foods or unit is found but does not exists in the database.
        </div>
        <div class="d-flex align-center mb-n4">
          <div class="mb-4">Select Parser</div>
          <BaseOverflowButton
            v-model="parser"
            btn-class="mx-2 mb-4"
            :items="[
              {
                text: 'Natural Language Processor ',
                value: 'nlp',
              },
              {
                text: 'Brute Parser',
                value: 'brute',
              },
            ]"
          />
        </div>
      </BaseCardSectionTitle>

      <div class="d-flex mt-n3 mb-4 justify-end" style="gap: 5px">
        <BaseButton cancel class="mr-auto" @click="$router.go(-1)"></BaseButton>
        <BaseButton color="info" @click="fetchParsed">
          <template #icon> {{ $globals.icons.foods }}</template>
          Parse All
        </BaseButton>
        <BaseButton save @click="saveAll"> Save All </BaseButton>
      </div>

      <v-expansion-panels v-model="panels" multiple>
        <v-expansion-panel v-for="(ing, index) in parsedIng" :key="index">
          <v-expansion-panel-header class="my-0 py-0" disable-icon-rotate>
            <template #default="{ open }">
              <v-fade-transition>
                <span v-if="!open" key="0"> {{ ing.input }} </span>
              </v-fade-transition>
            </template>
            <template #actions>
              <v-icon left :color="isError(ing) ? 'error' : 'success'">
                {{ isError(ing) ? $globals.icons.alert : $globals.icons.check }}
              </v-icon>
              <div class="my-auto" :color="isError(ing) ? 'error-text' : 'success-text'">
                {{ ing.confidence ? asPercentage(ing.confidence.average) : "" }}
              </div>
            </template>
          </v-expansion-panel-header>
          <v-expansion-panel-content class="pb-0 mb-0">
            <RecipeIngredientEditor v-model="parsedIng[index].ingredient" />
            {{ ing.input }}
            <v-card-actions>
              <v-spacer></v-spacer>
              <BaseButton
                v-if="errors[index].foodError && errors[index].foodErrorMessage !== ''"
                color="warning"
                small
                @click="createFood(ing.ingredient.food, index)"
              >
                {{ errors[index].foodErrorMessage }}
              </BaseButton>
            </v-card-actions>
          </v-expansion-panel-content>
        </v-expansion-panel>
      </v-expansion-panels>
    </v-container>
  </v-container>
</template>

<script lang="ts">
import { defineComponent, ref, useRoute, useRouter } from "@nuxtjs/composition-api";
import { invoke, until } from "@vueuse/core";
import {
  CreateIngredientFood,
  CreateIngredientUnit,
  IngredientFood,
  IngredientUnit,
  ParsedIngredient,
  RecipeIngredient,
} from "~/lib/api/types/recipe";
import RecipeIngredientEditor from "~/components/Domain/Recipe/RecipeIngredientEditor.vue";
import { useUserApi } from "~/composables/api";
import { useRecipe } from "~/composables/recipes";
import { useFoodData, useFoodStore, useUnitStore } from "~/composables/store";
import { Parser } from "~/lib/api/user/recipes/recipe";

interface Error {
  ingredientIndex: number;
  unitError: boolean;
  unitErrorMessage: string;
  foodError: boolean;
  foodErrorMessage: string;
}

export default defineComponent({
  components: {
    RecipeIngredientEditor,
  },
  setup() {
    const panels = ref<number[]>([]);

    const route = useRoute();
    const router = useRouter();
    const slug = route.value.params.slug;
    const api = useUserApi();

    const { recipe, loading } = useRecipe(slug);

    invoke(async () => {
      await until(recipe).not.toBeNull();

      fetchParsed();
    });

    const ingredients = ref<any[]>([]);

    // =========================================================
    // Parser Logic
    const parser = ref<Parser>("nlp");
    const parsedIng = ref<ParsedIngredient[]>([]);

    async function fetchParsed() {
      if (!recipe.value || !recipe.value.recipeIngredient) {
        return;
      }
      const raw = recipe.value.recipeIngredient.map((ing) => ing.note ?? "");
      const { data } = await api.recipes.parseIngredients(parser.value, raw);

      if (data) {
        // When we send the recipe ingredient text to be parsed, we lose the reference to the original unparsed ingredient.
        // Generally this is fine, but if the unparsed ingredient had a title, we lose it; we add back the title for each ingredient here.
        try {
          for (let i = 0; i < recipe.value.recipeIngredient.length; i++) {
            data[i].ingredient.title = recipe.value.recipeIngredient[i].title;
          }
        } catch (TypeError) {
          console.error("Index Mismatch Error during recipe ingredient parsing; did the number of ingredients change?");
        }

        parsedIng.value = data;

        errors.value = data.map((ing, index: number) => {
          const unitError = !checkForUnit(ing.ingredient.unit);
          const foodError = !checkForFood(ing.ingredient.food);

          let unitErrorMessage = "";
          let foodErrorMessage = "";

          if (unitError || foodError) {
            if (unitError) {
              if (ing?.ingredient?.unit?.name) {
                unitErrorMessage = `Create missing unit '${ing?.ingredient?.unit?.name || "No unit"}'`;
              }
            }

            if (foodError) {
              if (ing?.ingredient?.food?.name) {
                foodErrorMessage = `Create missing food '${ing.ingredient.food.name || "No food"}'?`;
              }
            }
          }
          panels.value.push(index);

          return {
            ingredientIndex: index,
            unitError,
            unitErrorMessage,
            foodError,
            foodErrorMessage,
          };
        });
      }
    }

    function isError(ing: ParsedIngredient) {
      if (!ing?.confidence?.average) {
        return true;
      }
      return !(ing.confidence.average >= 0.75);
    }

    function asPercentage(num: number | undefined): string {
      if (!num) {
        return "0%";
      }

      return Math.round(num * 100).toFixed(2) + "%";
    }

    // =========================================================
    // Food and Ingredient Logic

    const foodStore = useFoodStore();
    const foodData = useFoodData();
    const { units } = useUnitStore();

    const errors = ref<Error[]>([]);

    function checkForUnit(unit?: IngredientUnit | CreateIngredientUnit) {
      if (!unit) {
        return false;
      }
      if (units.value && unit?.name) {
        return units.value.some((u) => u.name === unit.name);
      }
      return false;
    }

    function checkForFood(food?: IngredientFood | CreateIngredientFood) {
      if (!food) {
        return false;
      }
      if (foodStore.foods.value && food?.name) {
        return foodStore.foods.value.some((f) => f.name === food.name);
      }
      return false;
    }

    async function createFood(food: CreateIngredientFood | undefined, index: number) {
      if (!food) {
        return;
      }

      foodData.data.name = food.name;
      await foodStore.actions.createOne(foodData.data);
      errors.value[index].foodError = false;
      foodData.reset();
    }

    // =========================================================
    // Save All Loginc
    async function saveAll() {
      let ingredients = parsedIng.value.map((ing) => {
        return {
          ...ing.ingredient,
          originalText: ing.input,
        } as RecipeIngredient;
      });

      ingredients = ingredients.map((ing) => {
        if (!foodStore.foods.value || !units.value) {
          return ing;
        }
        // Get food from foods
        ing.food = foodStore.foods.value.find((f) => f.name === ing.food?.name);

        // Get unit from units
        ing.unit = units.value.find((u) => u.name === ing.unit?.name);
        return ing;
      });

      if (!recipe.value || !recipe.value.slug) {
        return;
      }

      recipe.value.recipeIngredient = ingredients;
      const { response } = await api.recipes.updateOne(recipe.value.slug, recipe.value);

      if (response?.status === 200) {
        router.push("/recipe/" + recipe.value.slug);
      }
    }

    return {
      parser,
      saveAll,
      createFood,
      errors,
      actions: foodStore.actions,
      workingFoodData: foodData,
      isError,
      panels,
      asPercentage,
      fetchParsed,
      parsedIng,
      recipe,
      loading,
      ingredients,
    };
  },
  head() {
    return {
      title: "Parser",
    };
  },
});
</script>
