import { useEffect, useState } from "react";
import Banner from "../components/Banner";
import { isRecipeMeal, type Recipe, type RecipeInstruction } from "../types/recipe";
import InstructionList from "../components/forms/InstructionList";
import ValidationErrorList from "../components/forms/ValidationErrorList";
import IngredientSection from "../components/forms/IngredientSection";
import { Reorder } from "motion/react";
import IngredientList from "../components/forms/IngredientList";
import RecipeCreateFormInput from "../components/inputs/RecipeCreateFormInput";
import RecipeCreateDropdownInput, { type RecipeCreateDropdownOption } from "../components/inputs/RecipeCreateFormDropdown";
import RecipeCreateFormTextArea from "../components/inputs/RecipeCreateFormTextArea";
import RecipeCreateFormWrapper from "../components/inputs/RecipeCreateFormWrapper";
import RecipeCreateFormTagsInputs from "../components/inputs/RecipeCreateFormTagsInput";
import { useIngredients } from "../hooks/useIngredients";
import { validateCreateRecipeForm } from "../hooks/validation";
import { CreateRecipe, EditRecipe, GetRecipe, IsRecipeOwner } from "../services/RecipeService";
import type { CreateRecipeRequest, EditRecipeRequest } from "../types/api/recipe";
import { isApiError, type ApiError } from "../types/api/error";
import { useNavigate, useSearchParams } from "react-router-dom";
import ROUTE_CONSTANTS from "../types/routes";

// TODO: Move these
export interface RecipeValidationEntry {
  id: string;
  valid: boolean;
}

export interface CreateRecipeFormEntries {
  title: boolean;
  description: boolean;
  prepTime: boolean;
  cookTime: boolean;
  servingSize: boolean;
  category: boolean;
  difficulty: boolean;
  ingredients: RecipeValidationEntry[];
  instructions: RecipeValidationEntry[];
  // TODO: Image
}

export interface CreateRecipeFormDirtyEntries {
  title: boolean;
  description: boolean;
  prepTime: boolean;
  cookTime: boolean;
  servingSize: boolean;
  category: boolean;
  difficulty: boolean;
  ingredients: Record<string, boolean>;
  instructions: Record<string, boolean>;
  // TODO: Image
}

/**
 * Classes which are applied to all of the input elements.
 */
const INPUT_CLASSES = "border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all shadow-sm";

/**
 * Options passed to the category dropdown.
 */
const CATEGORY_OPTIONS: RecipeCreateDropdownOption[] = [
  { value: "", name: "Select a category" },
  { value: "breakfast", name: "Breakfast" },
  { value: "lunch", name: "Lunch" },
  { value: "dinner", name: "Dinner" },
  { value: "dessert", name: "Dessert" },
  { value: "snack", name: "Snack" },
  { value: "side", name: "Side" },
  { value: "other", name: "Other" },
];

/**
 * Options passed to the difficulty dropdown.
 */
const DIFFICULTY_OPTIONS: RecipeCreateDropdownOption[] = [
  { value: "", name: "Select a difficulty" },
  { value: "1", name: "Beginner" },
  { value: "2", name: "Easy" },
  { value: "3", name: "Intermediate" },
  { value: "4", name: "Challenging" },
];

export default function Create() {
  // Inputs
  const [title, setTitle] = useState<string>("");
  const [description, setDescription] = useState<string>("");
  const [tags, setTags] = useState<string[]>([]);
  const [prepTime, setPrepTime] = useState<string>("");
  const [cookTime, setCookTime] = useState<string>("");
  const [servingSize, setServingSize] = useState<string>("");
  const [category, setCategory] = useState<string>("");
  const [difficulty, setDifficulty] = useState<string>("");

  const [instructions, setInstructions] = useState<RecipeInstruction[]>([
    { Id: crypto.randomUUID(), Content: "" }
  ]);

  // Validation State
  const [validation, setValidation] = useState<CreateRecipeFormEntries>({
    title: true,
    description: true,
    prepTime: true,
    cookTime: true,
    servingSize: true,
    category: true,
    difficulty: true,
    ingredients: [],
    instructions: [],
  });
  const [isFormValid, setIsFormValid] = useState<boolean>(false);

  // Dirty State
  const [dirty, setDirty] = useState<CreateRecipeFormDirtyEntries>({
    title: false,
    description: false,
    prepTime: false,
    cookTime: false,
    servingSize: false,
    category: false,
    difficulty: false,
    ingredients: {},
    instructions: {},
  });

  const navigate = useNavigate();

  // Functions
  const createRecipe = async (): Promise<void> => {
    // Exit if not valid recipe meal
    // This is a REQUIRED typescript check.
    if (!isRecipeMeal(category)) {
      console.error("[ERROR] Recipe meal is invalid.");
      return;
    }

    const recipe: CreateRecipeRequest = {
      Title: title,
      Description: description,
      Instructions: instructions,
      Serves: Number(servingSize),
      Difficulty: Number(difficulty),
      Duration: {
        Prep: Number(prepTime),
        Cook: Number(cookTime),
        Total: Number(prepTime) + Number(cookTime)
      },
      Category: category,
      Ingredients: ingredients,
      Sections: sections,
      Tags: tags,
    };


    const response = await CreateRecipe(recipe);
    if (isApiError(response)) {
      console.error(response);
      return;
    }
    // TODO: Success toast!
    await navigate(ROUTE_CONSTANTS.Recipe(response.Id));
  };

  const editRecipe = async (): Promise<void> => {
    const recipeId = Number(editingId);

    if (!recipeId) {
      console.error("[ERROR] Invalid reicpe ID");
      return;
    }

    // Exit if not valid recipe meal
    // This is a REQUIRED typescript check.
    if (!isRecipeMeal(category)) {
      console.error("[ERROR] Recipe meal is invalid.");
      return;
    }

    const recipe: EditRecipeRequest = {
      Id: recipeId,
      Title: title,
      Description: description,
      Instructions: instructions,
      Serves: Number(servingSize),
      Difficulty: Number(difficulty),
      Duration: {
        Prep: Number(prepTime),
        Cook: Number(cookTime),
        Total: Number(prepTime) + Number(cookTime)
      },
      Category: category,
      Ingredients: ingredients,
      Sections: sections,
      Tags: tags,
    };

    const response = await EditRecipe(recipe);
    if (isApiError(response)) {
      console.error(response);
      return;
    }
    // TODO: Success toast!
    await navigate(ROUTE_CONSTANTS.Recipe(response.Id));
  }

  // Import ingredients
  const {
    sections,
    ingredients,
    setIngredients,
    setSections,
    sectionChange,
    ingredientChange,
    setSectionIngredients,
    addIngredient,
    removeIngredient,
    addSection,
    removeSection,
  } = useIngredients();

  // Instruction handlers
  const addInstructionHandler = () => {
    setInstructions([...instructions, { Id: crypto.randomUUID(), Content: "" }]);
  }

  // Dirty handlers
  const markInstructionDirty = (id: string) => {
    setDirty(prev => ({
      ...prev,
      instructions: {
        ...prev.instructions,
        [id]: true,
      },
    }));
  };

  const markIngredientDirty = (id: string) => {
    setDirty(prev => ({
      ...prev,
      ingredients: {
        ...prev.ingredients,
        [id]: true,
      },
    }));
  };

  const markAllIngredientsDirty = (): Record<string, boolean> => {
    const all: Record<string, boolean> = {};
    for (const ing of ingredients) all[ing.Id] = true;
    return all;
  };

  const markAllInstructionsDirty = (): Record<string, boolean> => {
    const all: Record<string, boolean> = {};
    for (const instr of instructions) all[instr.Id] = true;
    return all;
  };

  // HANDLERS
  const submitHandler = () => {
    // If any inputs are not dirty, simply dirty them all and return
    const scalar_dirty = [
      dirty.title,
      dirty.description,
      dirty.prepTime,
      dirty.cookTime,
      dirty.servingSize,
      dirty.category,
      dirty.difficulty,
    ];
    const ingredients_dirty = Object.values(dirty.ingredients).every(Boolean);
    const instructions_dirty = Object.values(dirty.instructions).every(Boolean);

    const all_dirty = scalar_dirty.every(Boolean) && ingredients_dirty && instructions_dirty;

    if (!all_dirty) {
      setDirty({
        title: true,
        description: true,
        prepTime: true,
        cookTime: true,
        servingSize: true,
        category: true,
        difficulty: true,
        ingredients: markAllIngredientsDirty(),
        instructions: markAllInstructionsDirty(),
      });
      return;
    }

    if (editingId) {
      void editRecipe();
    } else {
      void createRecipe();
    }
  }


  // EFFECTS 
  useEffect(() => {
    // Execute validation every time inputs change
    setValidation(
      validateCreateRecipeForm(
        { title, description, prepTime, cookTime, servingSize, category, difficulty, ingredients, instructions },
        dirty
      )
    );
  }, [title, description, prepTime, cookTime, servingSize, category, difficulty, instructions, ingredients, dirty]);

  useEffect(() => {
    // The form is only valid when every item is valid!
    const bools_valid = Object.values(validation).filter(x => typeof x === "boolean").every(x => x);
    const ingredients_valid = validation.ingredients.filter(x => !x.valid).length === 0;
    const instructions_valid = validation.instructions.filter(x => !x.valid).length === 0;
    setIsFormValid(bools_valid && ingredients_valid && instructions_valid);
  }, [validation, dirty]);

  // EDITING IMPLEMENTATION
  const [searchParams] = useSearchParams();
  const editingId = searchParams.get("edit");

  // Functions
  const getIsAuthor = async (recipeId: number) => {
    if (!recipeId) return;
    const response = await IsRecipeOwner(recipeId);
    if (isApiError(response)) {
      console.error(response.message);
      return;
    }

    return response;
  }

  const setForm = (recipe: Recipe) => {
    setTitle(recipe.Title);
    setDescription(recipe.Description);
    setTags(recipe.Tags.map(tag => tag.Name));
    setPrepTime(recipe.Duration.Prep.toString());
    setCookTime(recipe.Duration.Cook.toString());
    setServingSize(recipe.Serves.toString());
    setCategory(recipe.Category);
    setDifficulty(recipe.Difficulty.toString());

    // Generate IDs for instructions and store them
    const instructionsWithIds: RecipeInstruction[] = recipe.Instructions.map(ins => ({
      Id: crypto.randomUUID(),
      Content: ins.Content
    }));
    setInstructions(instructionsWithIds);

    setSections(recipe.Sections);

    // Manually set the local state, not ideal but it works
    setIngredients(recipe.Ingredients.sort((a, b) => a.SectionId.localeCompare(b.SectionId)));

    const ingredientsDirty: Record<string, boolean> = {};
    recipe.Ingredients.forEach(ing => {
      ingredientsDirty[ing.Id] = true;
    });

    const instructionsDirty: Record<string, boolean> = {};
    instructionsWithIds.forEach(ins => {
      instructionsDirty[ins.Id] = true;
    });

    setDirty({
      title: true,
      description: true,
      prepTime: true,
      cookTime: true,
      servingSize: true,
      category: true,
      difficulty: true,
      ingredients: ingredientsDirty,
      instructions: instructionsDirty,
    });
  };

  // Effects
  useEffect(() => {
    const id = Number(editingId);
    if (!id) return;

    const execute = async () => {
      const isAuthor = await getIsAuthor(id);

      if (!isAuthor) {
        console.error("User is not the owner, and cannot edit this recipe.");
        return;
      }

      const result: Recipe | ApiError = await GetRecipe(id);
      if (isApiError(result)) {
        console.error(result.message);
        return;
      }

      setForm(result);
    };

    void execute();
  }, [editingId]);

  return (
    <>
      <Banner content={editingId ? "Edit Your Recipe" : "Create Your Masterpiece"} />
      <div className="mx-4 md:mx-16 my-8">
        <p className="mb-8">
          {editingId ? (
            <>
              Welcome back! Update your recipe by modifying any of the details below, including the recipe's name,
              description, category, duration, difficulty, ingredients, and instructions. You can add or remove
              ingredients and instruction steps using the dedicated buttons, and update the recipe image if desired.
              All required fields are marked with an <span className="text-red-500">*</span>. Once you're happy
              with your changes, hit the "Update Recipe" button to save your edits!
            </>
          ) : (
            <>
              Welcome to the Recipe Creation Wizard! Simply fill in the details about your culinary creation,
              including the recipe's name, a description, and other specifics like its category, duration,
              and difficulty. Don't forget to dynamically add all your ingredients and instructions using
              the dedicated buttons, and feel free to upload an appealing image. All required fields are
              marked with an <span className="text-red-500">*</span>. Once everything looks perfect, just
              hit the "Create Recipe" button to share your masterpiece!
            </>
          )}
        </p>
        <div>
          {/* Title Input */}
          <RecipeCreateFormInput
            label="Recipe Title"
            name="title"
            desc="Please provide a unique title for your recipe. This is the most important part!"
            placeholder="e.g., Classic Chicken Curry"
            required
            valid={validation.title}
            value={title}
            setValue={setTitle}
            setDirty={setDirty}
            maxLength={128}
            error="Please enter a title. Between 1-128 characters."
            classes={INPUT_CLASSES}
          />

          {/* Description Input */}
          <RecipeCreateFormTextArea
            label="Description"
            name="description"
            desc="Please provide a description for your recipe. This can be short and sweet or long and detailed!"
            placeholder="A brief description of your delicious recipe..."
            required
            valid={validation.description}
            value={description}
            setValue={setDescription}
            setDirty={setDirty}
            error="Please enter a description. Between 1-1000 characters."
            rows={4}
            maxLength={1024}
            minLength={1}
            parentClasses="my-4"
            classes={INPUT_CLASSES}
          />

          {/* Tag Input */}
          <RecipeCreateFormTagsInputs
            tags={tags}
            setTags={setTags}
            classes={INPUT_CLASSES}
          />

          {/* Time Input */}
          <div className="my-4 flex gap-x-2">
            <RecipeCreateFormInput
              label="Prep Time"
              name="prepTime"
              type="number"
              desc="Please provide the estimated prep time (minutes)."
              placeholder="e.g., 20"
              required
              valid={validation.prepTime}
              value={prepTime}
              setValue={setPrepTime}
              setDirty={setDirty}
              error="Please enter a time (minutes)."
              parentClasses="flex-grow w-1/3"
              min="0"
              max="300"
              classes={INPUT_CLASSES}
            />

            <RecipeCreateFormInput
              label="Cook Time"
              name="cookTime"
              type="number"
              desc="Please provide the estimated cook time (minutes)."
              placeholder="e.g., 45"
              required
              valid={validation.cookTime}
              value={cookTime}
              setValue={setCookTime}
              setDirty={setDirty}
              error="Please enter a time (minutes)."
              parentClasses="flex-grow w-1/3"
              min="0"
              max="300"
              classes={INPUT_CLASSES}
            />

            <RecipeCreateFormInput
              label="Serving Size"
              name="servingSize"
              type="number"
              desc="Please provide the estimated serving size."
              placeholder="e.g., 4"
              required
              valid={validation.servingSize}
              value={servingSize}
              setValue={setServingSize}
              setDirty={setDirty}
              error="Please enter a serving size."
              parentClasses="flex-grow w-1/3"
              min="1"
              max="127"
              classes={INPUT_CLASSES}
            />
          </div>

          {/* Dropdown Inputs */}
          <div className="my-4 flex gap-x-2">
            <RecipeCreateDropdownInput
              label="Category"
              name="category"
              desc="Please provide the meal category."
              required
              valid={validation.category}
              value={category}
              setValue={setCategory}
              setDirty={setDirty}
              options={CATEGORY_OPTIONS}
              error="Please select a category."
              parentClasses="flex-grow w-1/3"
              classes={INPUT_CLASSES}
            />

            <RecipeCreateDropdownInput
              label="Difficulty"
              name="difficulty"
              desc="Please provide a baseline difficulty."
              required
              valid={validation.difficulty}
              value={difficulty}
              setValue={setDifficulty}
              setDirty={setDirty}
              options={DIFFICULTY_OPTIONS}
              error="Please select a difficulty."
              parentClasses="flex-grow w-1/3"
              classes={INPUT_CLASSES}
            />
          </div>

          {/* Ingredient Inputs */}
          <RecipeCreateFormWrapper
            label="Ingredients"
            name="ingredients"
            desc="Please provide a list of ingredients and their quantities. Ingredients can be grouped together by item using the group headers. If only a single group is defined, the name will be ignored and they will be displayed without a heading."
            required
            parentClasses="my-4"
          >
            <Reorder.Group
              axis="y"
              values={sections}
              onReorder={setSections}
              className="animate-none"
            >
              {sections.map((section, i) => (
                <IngredientSection
                  key={section.Id}
                  section={section}
                  onChange={sectionChange}
                  removeIngredientSectionHandler={removeSection}
                  allowDelete={sections.length > 1}
                >
                  <IngredientList
                    classes={INPUT_CLASSES}
                    section={section}
                    ingredients={ingredients}
                    setSectionIngredients={setSectionIngredients}
                    ingredientChangeHandler={ingredientChange}
                    removeIngredientHandler={removeIngredient}
                    validList={validation.ingredients}
                    dirtyList={dirty.ingredients}
                    markDirty={markIngredientDirty}
                  />
                  <button
                    onClick={() => addIngredient(section.Id)}
                    className="w-fit px-3 py-1.5 text-blue-800 font-semibold text-xs md:text-sm rounded-lg hover:bg-gray-100 duration-300 cursor-pointer"
                  >
                    Add ingredient
                  </button>
                  <button
                    onClick={() => addSection(i)}
                    className="w-fit px-3 py-1.5 text-blue-800 font-semibold text-xs md:text-sm rounded-lg hover:bg-gray-100 duration-300 cursor-pointer"
                  >
                    Add group header
                  </button>
                </IngredientSection>
              ))}
            </Reorder.Group>
          </RecipeCreateFormWrapper>

          {/* Instruction Inputs */}
          <RecipeCreateFormWrapper
            label="Instructions"
            name="instructions"
            desc="Please provide a list of instructions. You do not need to include step number, they will be added automatically!"
            required
            parentClasses="my-4"
          >
            <>
              <InstructionList
                instructions={instructions}
                setInstructions={setInstructions}
                validList={validation.instructions}
                dirtyList={dirty.instructions}
                markDirty={markInstructionDirty}
              />
              <button
                onClick={addInstructionHandler}
                className="w-fit px-3 py-1.5 text-blue-800 font-semibold text-xs md:text-sm rounded-lg hover:bg-gray-100 duration-300 cursor-pointer"
              >
                Add Instruction Step
              </button>
            </>
          </RecipeCreateFormWrapper>

          {/* TODO: Images Input */}
          <div className="flex flex-col my-4">
            <label htmlFor="image" className="text-sm">
              Recipe Image
            </label>
            <p className="text-xs pt-1 pb-2 text-gray-700">
              Please provide an image of your creation. This is optional but is a nice touch!
            </p>
            <input
              type="file"
              accept="image/*"
              name="image"
              id="image"
              className="my-2 block w-full text-sm text-placeholder file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:bg-blue-100 file:text-blue-700 cursor-pointer"
            />
          </div>

          {/* Display the reason for the invalidation */}
          <ValidationErrorList validation={validation} />

          <button
            onClick={submitHandler}
            disabled={!isFormValid}
            className={`${isFormValid ? "bg-gradient-to-r from-blue-200 to-purple-200 cursor-pointer" : "bg-gray-200 text-gray-500 cursor-not-allowed"} w-full py-2 rounded-lg text-lg shadow-md`}
          >
            {editingId ? "Save Changes" : "Create Recipe"}
          </button>
        </div>
      </div>
    </>
  );
}
