package main

import (
	"brainminder.speedtech.it/internal/password"
	"bytes"
	"fmt"
	"github.com/justinas/nosurf"
	"net/http"
	"strconv"
	"strings"
	"time"

	"brainminder.speedtech.it/internal/request"
	"brainminder.speedtech.it/internal/response"
	"brainminder.speedtech.it/internal/validator"
	"brainminder.speedtech.it/models"
	"github.com/alexedwards/flow"
)

type itemForm struct {
	Id                  int64    `form:"Id"`
	TypeId              int64    `form:"Type_id"`
	Title               string   `form:"Title"`
	Summary             string   `form:"Summary"`
	Description         string   `form:"Description"`
	Notebooks           []string `form:"Notebooks"`
	Tags                string   `form:"Tags"`
	OnDashboard         int      `form:"On_dashboard"`
	Categories          []string `form:"Categories"`
	TypeIcon            string
	TypeTitle           string
	TypeShowSummary     int
	TypeShowDescription int
	FieldsSection       map[string][]models.Field
	FieldsValues        map[int64]map[int]string
	Relations           []models.ItemRelation
	Shares              []models.ItemShare
	Validator           validator.Validator `form:"-"`
}

type itemShareForm struct {
	Id             int64               `form:"Id"`
	ItemId         int64               `form:"Item_id"`
	Token          string              `form:"Token"`
	StartDatetime  string              `form:"Start_datetime"`
	EndDatetime    string              `form:"End_datetime"`
	Password       string              `form:"Password"`
	PermissionEdit int                 `form:"Permission_edit"`
	Validator      validator.Validator `form:"-"`
}

func (form *itemForm) Validate(w http.ResponseWriter, r *http.Request, app *application, data map[string]any) bool {
	var fullBuf = new(bytes.Buffer)

	form.Validator.CheckField(form.Title != "", "Title", "Title is required")
	//form.Validator.CheckField(form.Description != "", "Description", "Description is required")

	if form.Validator.HasErrors() {
		w.Header().Add("HX-Retarget", "#message")
		w.Header().Add("HX-Trigger-After-Settle", `{"showMessage":""}`)

		data["messageType"] = "failure"
		data["messageContent"] = "Impossible to save the item"
		data["messageFieldErrors"] = form.Validator.FieldErrors

		err := response.HXFragment(fullBuf, []string{"partials/message.tmpl"}, "message", data)
		if err != nil {
			app.serverError(w, r, err)
		}
		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item response buffer : ", err)
		}
		w.WriteHeader(http.StatusUnprocessableEntity)
		return false
	}

	return true
}

func (form *itemShareForm) Validate(w http.ResponseWriter, r *http.Request, app *application, data map[string]any) bool {
	var fullBuf = new(bytes.Buffer)

	//form.Validator.CheckField(form.Title != "", "Title", "Title is required")
	//form.Validator.CheckField(form.Description != "", "Description", "Description is required")

	if form.Validator.HasErrors() {
		w.Header().Add("HX-Retarget", "#message")
		w.Header().Add("HX-Trigger-After-Settle", `{"showMessage":""}`)

		data["messageType"] = "failure"
		data["messageContent"] = "Impossible to save the item"
		data["messageFieldErrors"] = form.Validator.FieldErrors

		err := response.HXFragment(fullBuf, []string{"partials/message.tmpl"}, "message", data)
		if err != nil {
			app.serverError(w, r, err)
		}
		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item share validation response buffer : ", err)
		}
		w.WriteHeader(http.StatusUnprocessableEntity)
		return false
	}

	return true
}

func (app *application) itemsRelationAdd(w http.ResponseWriter, r *http.Request) {
	data := app.newTemplateData(r)
	itemModel := models.NewItemModel(app.db)
	categoryModel := &models.CategoryModel{DB: app.db}
	var fullBuf = new(bytes.Buffer)
	relatedItemId, _ := strconv.ParseInt(flow.Param(r.Context(), "related_item_id"), 10, 64)

	relatedItem, _, _ := itemModel.One(relatedItemId)
	data["relatedItem"] = relatedItem
	data["categoriesMap"] = categoryModel.AllAsMap()

	err := response.HXFragment(fullBuf, []string{"items/add_relation.tmpl"}, "item:add_relation", data)
	if err != nil {
		app.serverError(w, r, err)
	}

	_, err = fullBuf.WriteTo(w)
	if err != nil {
		app.logError("Building item relation add response buffer : ", err)
	}
	w.WriteHeader(http.StatusOK)
}

func (app *application) itemsSearch(w http.ResponseWriter, r *http.Request) {
	data := app.newTemplateData(r)
	itemModel := models.NewItemModel(app.db)
	var fullBuf = new(bytes.Buffer)

	switch r.Method {
	case http.MethodPost:
		categoryModel := &models.CategoryModel{DB: app.db}
		data["categoriesMap"] = categoryModel.AllAsMap()
		data["offset"] = 0

		err := r.ParseForm()
		if err != nil {
			app.serverError(w, r, err)
			return
		}

		criteria := make(map[string]any)
		criteria["notebook_id"] = app.getCurrentNotebookId(r)

		rows, _, _ := itemModel.Search(r.PostForm.Get("SearchText"), criteria)
		data["items"] = &rows
		err = response.HXFragment(fullBuf, []string{"items/all_list.tmpl", "items/all_list_rows.tmpl", "items/add_to_dashboard.tmpl", "items/remove_from_dashboard.tmpl"}, "itemsAll:list", data)
		if err != nil {
			app.serverError(w, r, err)
		}

		if r.Header.Get("HX-Target") == "page-content" {
			err = response.HXFragmentOOB(fullBuf, []string{"items/all_list_title.tmpl"}, "page:title", data, "page-title")
			if err != nil {
				app.serverError(w, r, err)
			}
		}

		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item search response buffer : ", err)
		}
	}
	w.WriteHeader(http.StatusOK)
}

func (app *application) itemsSearchForRelations(w http.ResponseWriter, r *http.Request) {

	data := app.newTemplateData(r)
	itemModel := models.NewItemModel(app.db)
	var fullBuf = new(bytes.Buffer)

	categoryModel := &models.CategoryModel{DB: app.db}
	data["categoriesMap"] = categoryModel.AllAsMap()
	data["item_id"], _ = strconv.ParseInt(flow.Param(r.Context(), "item_id"), 10, 64)

	err := r.ParseForm()
	if err != nil {
		app.serverError(w, r, err)
		return
	}

	criteria := make(map[string]any)
	criteria["notebook_id"] = app.getCurrentNotebookId(r)

	rows, _, _ := itemModel.Search(r.Form.Get("SearchText"), criteria)
	data["items"] = &rows
	err = response.HXFragment(fullBuf, []string{"items/list_for_relations.tmpl"}, "items:list-for-relations", data)
	if err != nil {
		app.serverError(w, r, err)
	}

	_, err = fullBuf.WriteTo(w)
	if err != nil {
		app.logError("Building item search for relations response buffer : ", err)
	}
	w.WriteHeader(http.StatusOK)
}

func (app *application) items(w http.ResponseWriter, r *http.Request) {
	data := app.newTemplateData(r)
	itemModel := models.NewItemModel(app.db)
	var fullBuf = new(bytes.Buffer)

	categoryModel := &models.CategoryModel{DB: app.db}
	data["categoriesMap"] = categoryModel.AllAsMap()

	params := r.URL.Query()

	offsetStr := r.URL.Query().Get("offset")
	if len(offsetStr) == 0 {
		offsetStr = "0"
	}
	offset, _ := strconv.ParseInt(offsetStr, 10, 64)
	data["offset"] = offset

	switch r.Method {
	case http.MethodGet:

		criteria := make(map[string]any)

		criteriaName := "itemsSearch"
		data["criteriaName"] = criteriaName

		criteriaParam := r.URL.Query()["criteriaName"]
		if len(criteriaParam) > 0 {
			if criteriaParam[0] == criteriaName {
				criteriaValues := app.getSessionValue(w, r, criteriaName)
				if criteriaValues != nil {
					criteria = criteriaValues.(map[string]any)
				}
			}
		} else {
			app.removeSessionValue(w, r, criteriaName)
		}

		criteria["notebook_id"] = app.getCurrentNotebookId(r)

		rows, _, _ := itemModel.Find(criteria, offset)

		data["items"] = &rows

		if r.Header.Get("HX-Request") == "true" {

			out := params.Get("out")

			if out == "rows" {
				err := response.HXFragment(fullBuf, []string{"items/all_list_rows.tmpl", "items/add_to_dashboard.tmpl", "items/remove_from_dashboard.tmpl"}, "itemsAll:list_rows", data)
				if err != nil {
					app.serverError(w, r, err)
				}
			} else {
				err := response.HXFragment(fullBuf, []string{"items/all_list.tmpl", "items/all_list_rows.tmpl", "items/add_to_dashboard.tmpl", "items/remove_from_dashboard.tmpl"}, "itemsAll:list", data)
				if err != nil {
					app.serverError(w, r, err)
				}

				err = response.HXFragmentOOB(fullBuf, []string{"items/all_list_title.tmpl"}, "page:title", data, "page-title")
				if err != nil {
					app.serverError(w, r, err)
				}
			}

			_, err := fullBuf.WriteTo(w)
			if err != nil {
				app.logError("Building items list response buffer : ", err)
			}
		} else {
			err := response.Page(w, http.StatusOK, data, []string{"items/index_all.tmpl", "items/all_list.tmpl", "items/all_list_rows.tmpl", "items/add_to_dashboard.tmpl", "items/remove_from_dashboard.tmpl", "items/all_list_title.tmpl"})
			if err != nil {
				app.serverError(w, r, err)
			}
		}
	case http.MethodPost:
		err := r.ParseForm()
		if err != nil {
			app.serverError(w, r, err)
			return
		}

		categoryId, _ := strconv.ParseInt(r.PostForm.Get("category_id"), 10, 64)
		criteria := map[string]any{
			"Title":       r.PostForm.Get("Title"),
			"Tags":        r.PostForm.Get("Tags"),
			"category_id": categoryId,
		}

		criteriaName := "itemsSearch"
		data["criteriaName"] = criteriaName
		app.saveSessionValue(w, r, criteriaName, criteria)

		rows, _, _ := itemModel.Find(criteria, offset)
		data["items"] = &rows
		err = response.HXFragment(fullBuf, []string{"items/all_list.tmpl", "items/all_list_rows.tmpl", "items/add_to_dashboard.tmpl", "items/remove_from_dashboard.tmpl"}, "itemsAll:list", data)
		if err != nil {
			app.serverError(w, r, err)
		}

		if r.Header.Get("HX-Target") == "page-content" {
			err = response.HXFragmentOOB(fullBuf, []string{"items/all_list_title.tmpl"}, "page:title", data, "page-title")
			if err != nil {
				app.serverError(w, r, err)
			}
		}

		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building items list response buffer on post: ", err)
		}
	}
	w.WriteHeader(http.StatusOK)
}

func (app *application) itemsType(w http.ResponseWriter, r *http.Request) {
	data := app.newTemplateData(r)

	typeId, _ := strconv.ParseInt(flow.Param(r.Context(), "type_id"), 10, 64)

	itemModel := models.NewItemModel(app.db)
	typeModel := &models.TypeModel{DB: app.db}
	aType, _, _ := typeModel.One(typeId)
	fieldModel := &models.FieldModel{DB: app.db}

	fields, _, _ := fieldModel.ByTypeOnList(typeId)
	data["type"] = &aType
	data["Fields"] = fields

	categoryModel := &models.CategoryModel{DB: app.db}
	data["categoriesMap"] = categoryModel.AllAsMap()

	params := r.URL.Query()

	offsetStr := params.Get("offset")
	if len(offsetStr) == 0 {
		offsetStr = "0"
	}
	offset, _ := strconv.ParseInt(offsetStr, 10, 64)
	data["offset"] = offset

	var fullBuf = new(bytes.Buffer)

	switch r.Method {
	case http.MethodGet:

		criteria := make(map[string]any)

		criteriaName := fmt.Sprintf("itemsTypeSearch_%d", typeId)
		data["criteriaName"] = criteriaName

		criteriaParam := r.URL.Query()["criteriaName"]
		if len(criteriaParam) > 0 {
			if criteriaParam[0] == criteriaName {
				criteriaValues := app.getSessionValue(w, r, criteriaName)
				if criteriaValues != nil {
					criteria = criteriaValues.(map[string]any)
				}
			}
		} else {
			app.removeSessionValue(w, r, criteriaName)
		}

		criteria["type_id"] = typeId
		criteria["notebook_id"] = app.getCurrentNotebookId(r)

		rows, _, _ := itemModel.Find(criteria, offset)
		for i, row := range rows {
			rows[i].FieldsValuesMap = fieldModel.GetFieldsValuesAsMap(row.Id)
		}

		data["items"] = rows

		if r.Header.Get("HX-Request") == "true" {
			out := params.Get("out")

			if out == "rows" {
				err := response.HXFragment(fullBuf, []string{"items/list_rows.tmpl", "items/add_to_dashboard.tmpl", "items/remove_from_dashboard.tmpl"}, "items:list_rows", data)
				if err != nil {
					app.serverError(w, r, err)
				}
			} else {
				err := response.HXFragment(fullBuf, []string{"items/list.tmpl", "items/list_rows.tmpl", "items/add_to_dashboard.tmpl", "items/remove_from_dashboard.tmpl"}, "items:list", data)
				if err != nil {
					app.serverError(w, r, err)
				}

				fullBuf.WriteString("<div hx-swap-oob=\"true\" id=\"page-title\">")
				err = response.HXFragmentOOB(fullBuf, []string{"items/list_title.tmpl"}, "page:title", data, "page-title")
				if err != nil {
					app.serverError(w, r, err)
				}
			}
			_, err := fullBuf.WriteTo(w)
			if err != nil {
				app.logError("Building items list by type response buffer : ", err)
			}
		} else {
			err := response.Page(w, http.StatusOK, data, []string{"items/index.tmpl", "items/list.tmpl", "items/list_rows.tmpl", "items/add_to_dashboard.tmpl", "items/remove_from_dashboard.tmpl", "items/list_title.tmpl"})
			if err != nil {
				app.serverError(w, r, err)
			}
		}
	case http.MethodPost:
		err := r.ParseForm()
		if err != nil {
			app.serverError(w, r, err)
			return
		}

		categoryId, _ := strconv.ParseInt(r.PostForm.Get("category_id"), 10, 64)
		criteria := map[string]any{
			"Title":       r.PostForm.Get("Title"),
			"Tags":        r.PostForm.Get("Tags"),
			"category_id": categoryId,
			"type_id":     typeId,
		}

		criteriaName := fmt.Sprintf("itemsTypeSearch_%d", typeId)
		data["criteriaName"] = criteriaName
		app.saveSessionValue(w, r, criteriaName, criteria)

		rows, _, _ := itemModel.Find(criteria, offset)
		for i, row := range rows {
			rows[i].FieldsValuesMap = fieldModel.GetFieldsValuesAsMap(row.Id)
		}
		data["items"] = &rows
		err = response.HXFragment(fullBuf, []string{"items/list.tmpl", "items/list_rows.tmpl", "items/add_to_dashboard.tmpl", "items/remove_from_dashboard.tmpl"}, "items:list", data)
		if err != nil {
			app.serverError(w, r, err)
		}
		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building items list by type response buffer on post : ", err)
		}
	}

	w.WriteHeader(http.StatusOK)
}

func (app *application) itemCreate(w http.ResponseWriter, r *http.Request) {
	itemModel := models.NewItemModel(app.db)
	fieldModel := &models.FieldModel{DB: app.db}
	categoryModel := &models.CategoryModel{DB: app.db}
	typeModel := &models.TypeModel{DB: app.db}

	typeId, _ := strconv.ParseInt(flow.Param(r.Context(), "type_id"), 10, 64)

	aType, _, _ := typeModel.One(typeId)

	data := app.newTemplateData(r)
	data["type"] = aType
	data["formAction"] = fmt.Sprint("/item/create/", typeId)
	data["formTarget"] = "#page-content"
	data["categories"] = categoryModel.AllAsOptions()
	data["categoriesMap"] = categoryModel.AllAsMap()

	criteria := map[string]any{
		"notebook_id": app.getCurrentNotebookId(r),
	}
	data["types"] = typeModel.FindAsOptions(criteria)

	notebookModel := &models.NotebookModel{DB: app.db}
	data["notebooks"] = notebookModel.AllAsOptions(false)

	var fullBuf = new(bytes.Buffer)
	var categories []string

	fieldsSection, _, _ := fieldModel.ByTypeSection(typeId)
	fieldsValues := make(map[int64]map[int]string)
	switch r.Method {
	case http.MethodGet:
		notebooks := []string{strconv.FormatInt(app.getCurrentNotebookId(r), 10)}

		data["item"] = itemForm{
			Title:               "",
			TypeId:              typeId,
			TypeShowSummary:     aType.ShowSummary,
			TypeShowDescription: aType.ShowDescription,
			Summary:             "",
			Description:         "",
			Tags:                "",
			FieldsSection:       fieldsSection,
			FieldsValues:        fieldsValues,
			Categories:          categories,
			Notebooks:           notebooks,
			Relations:           nil,
			Shares:              nil,
		}

		if r.Header.Get("HX-Request") == "true" {

			err := response.HXFragment(fullBuf, []string{"items/form.tmpl", "items/fields.tmpl", "items/relations.tmpl", "items/shares.tmpl"}, "page:content", data)
			if err != nil {
				app.serverError(w, r, err)
			}

			err = response.HXFragmentOOB(fullBuf, []string{"items/create_title.tmpl"}, "page:title", data, "page-title")
			if err != nil {
				app.serverError(w, r, err)
			}

			w.Header().Add("HX-Trigger-After-Settle", `{"activateEasyMDE":"item-description"}`)
			_, err = fullBuf.WriteTo(w)
			if err != nil {
				app.logError("Building item create form buffer : ", err)
			}
		} else {
			err := response.Page(w, http.StatusOK, data, []string{"items/create_title.tmpl", "items/relations.tmpl", "items/form.tmpl", "items/fields.tmpl", "items/form_jscode.tmpl", "items/shares.tmpl"})
			if err != nil {
				app.serverError(w, r, err)
			}
		}

	case http.MethodPost:
		var itemFromForm itemForm
		var itemId int64

		err := request.DecodePostForm(r, &itemFromForm)
		if err != nil {
			app.serverError(w, r, err)
		}

		if !itemFromForm.Validate(w, r, app, data) {
			return
		}

		item := &models.Item{
			TypeId:      itemFromForm.TypeId,
			Title:       itemFromForm.Title,
			Summary:     itemFromForm.Summary,
			Description: itemFromForm.Description,
			Tags:        itemFromForm.Tags,
			OnDashboard: itemFromForm.OnDashboard,
		}

		notebooksStr := strings.Join(itemFromForm.Notebooks, "|")
		if len(notebooksStr) > 0 {
			notebooksStr = "|" + notebooksStr + "|"
		}
		item.Notebooks = notebooksStr

		categoriesStr := strings.Join(itemFromForm.Categories, "|")
		if len(categoriesStr) > 0 {
			categoriesStr = "|" + categoriesStr + "|"
		}
		item.Categories = categoriesStr

		itemId, err = itemModel.Create(item)
		item.Id = itemId
		if err != nil {
			app.badRequest(w, err)
			return
		}

		fieldsValues := make(map[int64]map[int]string)
		for name, values := range r.PostForm {
			idS, found := strings.CutPrefix(name, "FieldsValues-")
			if found {
				parts := strings.Split(idS, "-")
				typeFieldId, _ := strconv.ParseInt(parts[0], 10, 64)
				counter, _ := strconv.Atoi(parts[1])

				_, foundKey := fieldsValues[typeFieldId]
				if !foundKey {
					fieldsValues[typeFieldId] = make(map[int]string)
				}
				fieldsValues[typeFieldId][counter] = values[0]
			}

			relatedIdS, found := strings.CutPrefix(name, "ItemRelation-New-")
			if found && len(values) > 0 {
				relatedItemId, _ := strconv.ParseInt(relatedIdS, 10, 64)
				err = itemModel.AddRelation(item.Id, relatedItemId, values[0])
				if err != nil {
					app.logError("Saving relation for item : ", err)
				}
			}
		}

		for typeFieldId, values := range fieldsValues {
			fieldModel.SaveValues(item.Id, typeFieldId, values)
		}

		item.TypeTitle = aType.Title
		fields, _, _ := fieldModel.ByType(typeId)
		err = itemModel.SaveKeywords(item, &fields, fieldsValues)
		if err != nil {
			app.logError("Saving keywords for item : ", err)
		}
		data["formAction"] = fmt.Sprint("/item/update/", itemId)
		data["formTarget"] = "#message"

		notebooks := strings.Split(strings.Trim(item.Notebooks, "|"), "|")
		categories := strings.Split(strings.Trim(item.Categories, "|"), "|")
		relations, _, _ := itemModel.GetRelations(item.Id)
		shares, _, _ := itemModel.GetShares(item.Id)

		data["item"] = itemForm{
			Id:                  itemId,
			TypeId:              typeId,
			TypeShowSummary:     aType.ShowSummary,
			TypeShowDescription: aType.ShowDescription,
			Title:               item.Title,
			Summary:             item.Summary,
			Description:         item.Description,
			Tags:                item.Tags,
			TypeIcon:            item.TypeIcon,
			TypeTitle:           item.TypeTitle,
			FieldsSection:       fieldsSection,
			FieldsValues:        fieldsValues,
			Notebooks:           notebooks,
			Categories:          categories,
			Relations:           relations,
			Shares:              shares,
		}

		err = response.HXFragment(fullBuf, []string{"items/form.tmpl", "items/fields.tmpl", "items/relations.tmpl", "items/shares.tmpl"}, "page:content", data)
		if err != nil {
			app.serverError(w, r, err)
		}

		err = response.HXFragmentOOB(fullBuf, []string{"items/update_title.tmpl"}, "page:title", data, "page-title")
		if err != nil {
			app.serverError(w, r, err)
		}

		dataMessage := make(map[string]string)
		dataMessage["messageType"] = "success"
		dataMessage["messageContent"] = "Item created successfully"
		err = response.HXFragmentOOB(fullBuf, []string{"partials/message.tmpl"}, "message", dataMessage, "message")
		if err != nil {
			app.serverError(w, r, err)
		}

		w.Header().Add("HX-Trigger-After-Settle", `{"activateEasyMDE":"item-description","showMessage":""}`)
		w.Header().Add("HX-Replace-Url", fmt.Sprint("/item/update/", itemId))
		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item update form buffer after post on create : ", err)
		}

		w.WriteHeader(http.StatusUnprocessableEntity)
	}
}

func (app *application) itemRead(w http.ResponseWriter, r *http.Request) {
	data := app.newTemplateData(r)
	itemId, _ := strconv.ParseInt(flow.Param(r.Context(), "item_id"), 10, 64)
	itemModel := models.NewItemModel(app.db)
	categoryModel := &models.CategoryModel{DB: app.db}
	fieldModel := &models.FieldModel{DB: app.db}
	relations, _, _ := itemModel.GetRelations(itemId)

	item, _, _ := itemModel.One(itemId)

	data["item"] = &item
	data["relations"] = relations
	data["categoriesMap"] = categoryModel.AllAsMap()
	data["fields"] = fieldModel.GetFieldsValues(itemId)

	if r.Header.Get("HX-Request") == "true" {
		var fullBuf = new(bytes.Buffer)

		err := response.HXFragment(fullBuf, []string{"items/read.tmpl", "items/relations_view.tmpl"}, "page:content", data)
		if err != nil {
			app.serverError(w, r, err)
		}

		err = response.HXFragmentOOB(fullBuf, []string{"items/read_title.tmpl"}, "page:title", data, "page-title")
		if err != nil {
			app.serverError(w, r, err)
		}

		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item read buffer : ", err)
		}
	} else {
		err := response.Page(w, http.StatusOK, data, []string{"items/read_title.tmpl", "items/relations_view.tmpl", "items/read.tmpl"})
		if err != nil {
			app.serverError(w, r, err)
		}
	}

	w.WriteHeader(http.StatusOK)
}

func (app *application) itemView(w http.ResponseWriter, r *http.Request) {
	data := app.newTemplateData(r)
	itemId, _ := strconv.ParseInt(flow.Param(r.Context(), "item_id"), 10, 64)
	itemModel := models.NewItemModel(app.db)
	categoryModel := &models.CategoryModel{DB: app.db}
	fieldModel := &models.FieldModel{DB: app.db}

	item, _, _ := itemModel.One(itemId)
	relations, _, _ := itemModel.GetRelations(itemId)

	data["item"] = &item
	data["relations"] = relations
	data["categoriesMap"] = categoryModel.AllAsMap()
	data["fields"] = fieldModel.GetFieldsValues(itemId)

	if r.Header.Get("HX-Request") == "true" {
		var fullBuf = new(bytes.Buffer)
		err := response.HXFragment(fullBuf, []string{"items/view.tmpl", "items/relations_view.tmpl"}, "page:content", data)
		if err != nil {
			app.serverError(w, r, err)
		}
		w.Header().Add("HX-Trigger-After-Settle", `{"showModalDialog": ""}`)
		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item view buffer : ", err)
		}
	} else {
		err := response.Page(w, http.StatusOK, data, []string{"items/read_title.tmpl", "items/relations_view.tmpl", "items/read.tmpl"})
		if err != nil {
			app.serverError(w, r, err)
		}

	}
	w.WriteHeader(http.StatusOK)
}

func (app *application) itemUpdate(w http.ResponseWriter, r *http.Request) {
	itemModel := models.NewItemModel(app.db)
	fieldModel := &models.FieldModel{DB: app.db}
	itemIdStr := flow.Param(r.Context(), "item_id")
	itemId, _ := strconv.ParseInt(flow.Param(r.Context(), "item_id"), 10, 64)
	item, _, _ := itemModel.One(itemId)
	fieldsSection, _, _ := fieldModel.ByTypeSection(item.TypeId)
	fieldsValues := fieldModel.GetFieldsValuesAsMap(item.Id)

	data := app.newTemplateData(r)
	data["formAction"] = "/item/update/" + itemIdStr
	data["formTarget"] = "#message"

	notebookModel := &models.NotebookModel{DB: app.db}
	data["notebooks"] = notebookModel.AllAsOptions(false)

	categoryModel := &models.CategoryModel{DB: app.db}
	data["categoriesMap"] = categoryModel.AllAsMap()

	var fullBuf = new(bytes.Buffer)

	switch r.Method {
	case http.MethodGet:

		notebooks := strings.Split(strings.Trim(item.Notebooks, "|"), "|")
		categories := strings.Split(strings.Trim(item.Categories, "|"), "|")
		relations, _, _ := itemModel.GetRelations(item.Id)
		shares, _, _ := itemModel.GetShares(item.Id)

		data["item"] = itemForm{
			Id:                  item.Id,
			TypeId:              item.TypeId,
			TypeShowSummary:     item.TypeShowSummary,
			TypeShowDescription: item.TypeShowDescription,
			Title:               item.Title,
			Summary:             item.Summary,
			Description:         item.Description,
			Tags:                item.Tags,
			OnDashboard:         item.OnDashboard,
			TypeIcon:            item.TypeIcon,
			TypeTitle:           item.TypeTitle,
			FieldsSection:       fieldsSection,
			FieldsValues:        fieldsValues,
			Categories:          categories,
			Notebooks:           notebooks,
			Relations:           relations,
			Shares:              shares,
		}

		data["categories"] = categoryModel.AllAsOptions()

		typeModel := &models.TypeModel{DB: app.db}
		criteria := map[string]any{
			"notebook_id": app.getCurrentNotebookId(r),
		}
		data["types"] = typeModel.FindAsOptions(criteria)

		if r.Header.Get("HX-Request") == "true" {

			err := response.HXFragment(fullBuf, []string{"items/form.tmpl", "items/fields.tmpl", "items/relations.tmpl", "items/shares.tmpl"}, "page:content", data)
			if err != nil {
				app.serverError(w, r, err)
			}

			err = response.HXFragmentOOB(fullBuf, []string{"items/update_title.tmpl"}, "page:title", data, "page-title")
			if err != nil {
				app.serverError(w, r, err)
			}

			w.Header().Add("HX-Trigger-After-Settle", `{"activateEasyMDE":"item-description"}`)
			_, err = fullBuf.WriteTo(w)
			if err != nil {
				app.logError("Building item update form buffer : ", err)
			}
		} else {
			err := response.Page(w, http.StatusOK, data, []string{"items/update_title.tmpl", "items/form.tmpl", "items/fields.tmpl", "items/relations.tmpl", "items/shares.tmpl", "items/form_jscode.tmpl"})
			if err != nil {
				app.serverError(w, r, err)
			}
		}

	case http.MethodPost:
		var itemFromForm itemForm

		err := request.DecodePostForm(r, &itemFromForm)
		if err != nil {
			app.serverError(w, r, err)
		}

		if !itemFromForm.Validate(w, r, app, data) {
			return
		}

		item.Title = itemFromForm.Title
		item.TypeId = itemFromForm.TypeId
		item.Summary = itemFromForm.Summary
		item.Description = itemFromForm.Description
		item.Tags = itemFromForm.Tags
		item.OnDashboard = itemFromForm.OnDashboard

		notebooksStr := strings.Join(itemFromForm.Notebooks, "|")
		if len(notebooksStr) > 0 {
			notebooksStr = "|" + notebooksStr + "|"
		}
		item.Notebooks = notebooksStr

		categoriesStr := strings.Join(itemFromForm.Categories, "|")
		if len(categoriesStr) > 0 {
			categoriesStr = "|" + categoriesStr + "|"
		}
		item.Categories = categoriesStr

		err = itemModel.Update(item)
		if err != nil {
			app.badRequest(w, err)
			return
		}

		fieldsValues := make(map[int64]map[int]string)
		for name, values := range r.PostForm {
			idS, found := strings.CutPrefix(name, "FieldsValues-")
			if found {
				parts := strings.Split(idS, "-")
				typeFieldId, _ := strconv.ParseInt(parts[0], 10, 64)
				counter, _ := strconv.Atoi(parts[1])

				valuesRemove, isMapContainsKey := r.PostForm["FieldsValuesToRemove-"+idS]
				if !isMapContainsKey || valuesRemove[0] == "0" {
					_, foundKey := fieldsValues[typeFieldId]
					if !foundKey {
						fieldsValues[typeFieldId] = make(map[int]string)
					}
					fieldsValues[typeFieldId][counter] = values[0]
				}
			}
			relatedIdS, found := strings.CutPrefix(name, "ItemRelation-New-")
			if found && len(values) > 0 {
				relatedItemId, _ := strconv.ParseInt(relatedIdS, 10, 64)
				err = itemModel.AddRelation(item.Id, relatedItemId, values[0])
				if err != nil {
					app.logError("Saving relation for item : ", err)
				}
			}

			idS, found = strings.CutPrefix(name, "ItemRelation-")
			if found && len(values) > 0 {
				ids := strings.Split(idS, "-")
				itemId, _ := strconv.ParseInt(ids[0], 10, 64)
				relatedItemId, _ := strconv.ParseInt(ids[1], 10, 64)
				valuesRemove, isMapContainsKey := r.PostForm["ItemRelation-ToRemove-"+idS]
				if isMapContainsKey && valuesRemove[0] == "1" {
					err = itemModel.DeleteRelation(itemId, relatedItemId)
					if err != nil {
						app.logError("Delete relation from item : ", err)
					}
				} else {
					err = itemModel.UpdateRelation(itemId, relatedItemId, values[0])
					if err != nil {
						app.logError("Update relation for item : ", err)
					}
				}
			}
			shareId, found := strings.CutPrefix(name, "ItemShare-ToRemove-")
			if found && len(values) > 0 {
				valuesRemove, isMapContainsKey := r.PostForm["ItemShare-ToRemove-"+shareId]
				if isMapContainsKey && valuesRemove[0] == "1" {
					shareIdInt, _ := strconv.ParseInt(shareId, 10, 64)
					err = itemModel.DeleteShare(shareIdInt)
					if err != nil {
						app.logError("Deleting share for item : ", err)
					}
				}
			}
		}

		for typeFieldId, values := range fieldsValues {
			fieldModel.SaveValues(item.Id, typeFieldId, values)
		}

		fields, _, _ := fieldModel.ByType(item.TypeId)
		err = itemModel.SaveKeywords(item, &fields, fieldsValues)
		if err != nil {
			app.logError("Saving keywords for item : ", err)
		}
		relations, _, _ := itemModel.GetRelations(item.Id)
		shares, _, _ := itemModel.GetShares(item.Id)

		data["item"] = itemForm{
			Title:     item.Title,
			TypeIcon:  item.TypeIcon,
			TypeTitle: item.TypeTitle,
			Relations: relations,
			Shares:    shares,
		}

		data["FieldsSection"] = fieldsSection
		data["FieldsValues"] = fieldsValues

		dataMessage := make(map[string]string)
		dataMessage["messageType"] = "success"
		dataMessage["messageContent"] = "Item saved successfully"
		err = response.HXFragment(fullBuf, []string{"partials/message.tmpl"}, "message", dataMessage)
		if err != nil {
			app.serverError(w, r, err)
		}

		err = response.HXFragmentOOB(fullBuf, []string{"items/update_title.tmpl"}, "page:title", data, "page-title")
		if err != nil {
			app.serverError(w, r, err)
		}

		err = response.HXFragmentOOB(fullBuf, []string{"items/relations.tmpl"}, "item:relations", data, "relations")
		if err != nil {
			app.serverError(w, r, err)
		}

		err = response.HXFragmentOOB(fullBuf, []string{"items/shares.tmpl"}, "item:shares", data, "shares")
		if err != nil {
			app.serverError(w, r, err)
		}

		data["uisection"] = "general"
		err = response.HXFragmentOOB(fullBuf, []string{"items/fields.tmpl"}, "items:fields", data, "fields-general")
		if err != nil {
			app.serverError(w, r, err)
		}

		_, found := fieldsSection["fields"]
		if found {
			data["uisection"] = "fields"
			err = response.HXFragmentOOB(fullBuf, []string{"items/fields.tmpl"}, "items:fields", data, "fields-fields")
			if err != nil {
				app.serverError(w, r, err)
			}
		}

		w.Header().Add("HX-Reswap", `beforeend`)
		w.Header().Add("HX-Trigger-After-Settle", `{"showMessage": ""}`)

		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item update form buffer after post on update : ", err)
		}

		w.WriteHeader(http.StatusUnprocessableEntity)
	}
}

func (app *application) itemDelete(w http.ResponseWriter, r *http.Request) {
	itemModel := models.NewItemModel(app.db)
	itemIdStr := flow.Param(r.Context(), "item_id")
	itemId, _ := strconv.Atoi(itemIdStr)

	_, err := itemModel.Delete(itemId)
	if err != nil {
		app.serverError(w, r, err)
	}
}

func (app *application) itemShare(w http.ResponseWriter, r *http.Request) {
	itemModel := models.NewItemModel(app.db)
	itemId, _ := strconv.ParseInt(flow.Param(r.Context(), "item_id"), 10, 64)
	item, _, _ := itemModel.One(itemId)

	data := app.newTemplateData(r)
	data["item"] = item
	data["publicBaseUrl"] = app.config.publicBaseURL

	switch r.Method {
	case http.MethodGet:
		var fullBuf = new(bytes.Buffer)

		data["shareToken"] = app.generateSecureToken(18)

		err := response.HXFragment(fullBuf, []string{"items/share.tmpl"}, "page:content", data)
		if err != nil {
			app.serverError(w, r, err)
		}

		w.Header().Add("HX-Trigger-After-Settle", `{"showModalDialog": ""}`)
		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item share form buffer : ", err)
		}
	case http.MethodPost:
		var itemShareFromForm itemShareForm

		err := request.DecodePostForm(r, &itemShareFromForm)
		if err != nil {
			app.serverError(w, r, err)
		}

		if !itemShareFromForm.Validate(w, r, app, data) {
			return
		}

		hashedPassword := ""
		if itemShareFromForm.Password != "" {
			hashedPassword, _ = password.Hash(itemShareFromForm.Password)
		}
		itemShare := &models.ItemShare{
			ItemId:         itemId,
			Token:          itemShareFromForm.Token,
			Password:       hashedPassword,
			PermissionEdit: itemShareFromForm.PermissionEdit,
			StartDatetime:  itemShareFromForm.StartDatetime,
			EndDatetime:    itemShareFromForm.EndDatetime,
		}

		itemShareModel := models.NewItemShareModel(app.db)
		_, err = itemShareModel.Create(itemShare)
		if err != nil {
			app.badRequest(w, err)
			return
		}
		var fullBuf = new(bytes.Buffer)

		dataMessage := make(map[string]string)
		dataMessage["messageType"] = "success"
		dataMessage["messageContent"] = "Item shared created successfully"
		err = response.HXFragment(fullBuf, []string{"partials/message.tmpl"}, "message", dataMessage)
		if err != nil {
			app.serverError(w, r, err)
		}

		w.Header().Add("HX-Reswap", `beforeend`)
		w.Header().Add("HX-Trigger-After-Swap", `{"showMessage": "", "closeModalDialog": ""}`)

		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item share form buffer on post : ", err)
		}
	}
}

func (app *application) itemShareEdit(w http.ResponseWriter, r *http.Request) {
	itemShareModel := models.NewItemShareModel(app.db)
	id, _ := strconv.ParseInt(flow.Param(r.Context(), "id"), 10, 64)
	itemShare, _, _ := itemShareModel.One(id)

	data := app.newTemplateData(r)
	data["itemShare"] = itemShare
	data["publicBaseUrl"] = app.config.publicBaseURL

	switch r.Method {
	case http.MethodGet:
		var fullBuf = new(bytes.Buffer)

		err := response.HXFragment(fullBuf, []string{"items/share_edit.tmpl"}, "page:content", data)
		if err != nil {
			app.serverError(w, r, err)
		}

		w.Header().Add("HX-Trigger-After-Swap", `{"showModalDialog": ""}`)
		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item share edit form buffer : ", err)
		}
	case http.MethodPost:
		var itemShareFromForm itemShareForm

		err := request.DecodePostForm(r, &itemShareFromForm)
		if err != nil {
			app.serverError(w, r, err)
		}

		if !itemShareFromForm.Validate(w, r, app, data) {
			return
		}

		hashedPassword := ""
		if itemShareFromForm.Password != "" {
			hashedPassword, _ = password.Hash(itemShareFromForm.Password)
		}
		itemShare := &models.ItemShare{
			Token:          itemShareFromForm.Token,
			Password:       hashedPassword,
			PermissionEdit: itemShareFromForm.PermissionEdit,
			StartDatetime:  itemShareFromForm.StartDatetime,
			EndDatetime:    itemShareFromForm.EndDatetime,
		}
		itemShare.Id = id

		err = itemShareModel.Update(itemShare)
		if err != nil {
			app.badRequest(w, err)
			return
		}
		var fullBuf = new(bytes.Buffer)

		dataMessage := make(map[string]string)
		dataMessage["messageType"] = "success"
		dataMessage["messageContent"] = "Item shared edited successfully"
		err = response.HXFragment(fullBuf, []string{"partials/message.tmpl"}, "message", dataMessage)
		if err != nil {
			app.serverError(w, r, err)
		}

		w.Header().Add("HX-Reswap", `beforeend`)
		w.Header().Add("HX-Trigger-After-Swap", `{"showMessage": "", "closeModalDialog": ""}`)

		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building item share form buffer on post : ", err)
		}
	}
}

func (app *application) itemShared(w http.ResponseWriter, r *http.Request) {
	token := flow.Param(r.Context(), "token")
	itemShareModel := models.NewItemShareModel(app.db)

	itemShare, found, _ := itemShareModel.OneByToken(token)
	data := map[string]any{"CSRFToken": nosurf.Token(r)}

	if !found {
		err := response.GuestPage(w, http.StatusOK, data, []string{"items/shared_not_found.tmpl"})
		if err != nil {
			app.serverError(w, r, err)
		}
	} else {
		itemValidTimeRange := true
		currentDateTime := time.Now()
		if strings.TrimSpace(itemShare.StartDatetime) != "" {
			startDateTime, _ := time.Parse(time.RFC3339, itemShare.StartDatetime+":00Z")
			if currentDateTime.Before(startDateTime) {
				itemValidTimeRange = false
			}
		}
		if strings.TrimSpace(itemShare.EndDatetime) != "" {
			endDateTime, _ := time.Parse(time.RFC3339, itemShare.EndDatetime+":00Z")
			if currentDateTime.After(endDateTime) {
				itemValidTimeRange = false
			}
		}

		if !itemValidTimeRange {
			err := response.GuestPage(w, http.StatusOK, data, []string{"items/sharing_elapsed.tmpl"})
			if err != nil {
				app.serverError(w, r, err)
			}
			return
		}

		//Does it need a password ?
		switch r.Method {
		case http.MethodGet:
			data["itemShare"] = itemShare
			err := response.GuestPage(w, http.StatusOK, data, []string{"items/shared.tmpl"})
			if err != nil {
				app.serverError(w, r, err)
			}
		case http.MethodPost:
			//Check session token
			//Check permission_edit
		}
	}
}

func (app *application) itemAddToDashboard(w http.ResponseWriter, r *http.Request) {
	itemModel := models.NewItemModel(app.db)
	itemId, _ := strconv.ParseInt(flow.Param(r.Context(), "item_id"), 10, 64)

	err := itemModel.AddToDashboard(itemId)
	if err != nil {
		app.serverError(w, r, err)
	}

	var fullBuf = new(bytes.Buffer)

	err = response.HXFragment(fullBuf, []string{"items/remove_from_dashboard.tmpl"}, "item:remove_from_dashboard", itemId)
	if err != nil {
		app.serverError(w, r, err)
	}

	_, err = fullBuf.WriteTo(w)
	if err != nil {
		app.logError("Building add item to the dashboard response: ", err)
	}
}

func (app *application) itemRemoveFromDashboard(w http.ResponseWriter, r *http.Request) {
	itemModel := models.NewItemModel(app.db)
	itemId, _ := strconv.ParseInt(flow.Param(r.Context(), "item_id"), 10, 64)

	err := itemModel.RemoveFromDashboard(itemId)
	if err != nil {
		app.serverError(w, r, err)
	}

	if r.Method == http.MethodPost {
		var fullBuf = new(bytes.Buffer)

		err := response.HXFragment(fullBuf, []string{"items/add_to_dashboard.tmpl"}, "item:add_to_dashboard", itemId)
		if err != nil {
			app.serverError(w, r, err)
		}

		_, err = fullBuf.WriteTo(w)
		if err != nil {
			app.logError("Building remove item from the dashboard response: ", err)
		}
	}
}
