<template>
  <v-card class="ma-0" style="overflow-x: auto;">
    <v-card-text class="ma-0 pa-0">
      <v-container fluid class="ma-0 pa-0">
        <VueDraggable
          v-model="fields"
          handle=".handle"
          :delay="250"
          :delay-on-touch-only="true"
          v-bind="{
            animation: 200,
            group: 'recipe-instructions',
            ghostClass: 'ghost',
          }"
          @start="drag = true"
          @end="onDragEnd"
        >
          <v-row
            v-for="(field, index) in fields"
            :key="field.id"
            class="d-flex flex-nowrap"
            style="max-width: 100%;"
          >
            <!-- drag handle -->
            <v-col
              :cols="config.items.icon.cols"
              :class="config.col.class"
              :style="config.items.icon.style"
            >
              <v-icon
                class="handle"
                :size="24"
                style="cursor: move;margin: auto;"
              >
                {{ $globals.icons.arrowUpDown }}
              </v-icon>
            </v-col>
            <!-- and / or  -->
            <v-col
              :cols="config.items.logicalOperator.cols"
              :class="config.col.class"
              :style="config.items.logicalOperator.style"
            >
              <v-select
                v-if="index"
                :model-value="field.logicalOperator"
                :items="[logOps.AND, logOps.OR]"
                item-title="label"
                item-value="value"
                variant="underlined"
                @update:model-value="setLogicalOperatorValue(field, index, $event as unknown as LogicalOperator)"
              >
                <template #chip="{ item }">
                  <span :class="config.select.textClass" style="width: 100%;">
                    {{ item.raw.label }}
                  </span>
                </template>
              </v-select>
            </v-col>
            <!-- left parenthesis -->
            <v-col
              v-if="showAdvanced"
              :cols="config.items.leftParens.cols"
              :class="config.col.class"
              :style="config.items.leftParens.style"
            >
              <v-select
                :model-value="field.leftParenthesis"
                :items="['', '(', '((', '(((']"
                variant="underlined"
                @update:model-value="setLeftParenthesisValue(field, index, $event)"
              >
                <template #chip="{ item }">
                  <span :class="config.select.textClass" style="width: 100%;">
                    {{ item.raw }}
                  </span>
                </template>
              </v-select>
            </v-col>
            <!-- field name -->
            <v-col
              :cols="config.items.fieldName.cols"
              :class="config.col.class"
              :style="config.items.fieldName.style"
            >
              <v-select
                chips
                :model-value="field.label"
                :items="fieldDefs"
                variant="underlined"
                item-title="label"
                @update:model-value="setField(index, $event)"
              >
                <template #chip="{ item }">
                  <span :class="config.select.textClass" style="width: 100%;">
                    {{ item.raw.label }}
                  </span>
                </template>
              </v-select>
            </v-col>
            <!-- relational operator -->
            <v-col
              :cols="config.items.relationalOperator.cols"
              :class="config.col.class"
              :style="config.items.relationalOperator.style"
            >
              <v-select
                v-if="field.type !== 'boolean'"
                :model-value="field.relationalOperatorValue"
                :items="field.relationalOperatorOptions"
                item-title="label"
                item-value="value"
                variant="underlined"
                @update:model-value="setRelationalOperatorValue(field, index, $event as unknown as RelationalKeyword | RelationalOperator)"
              >
                <template #chip="{ item }">
                  <span :class="config.select.textClass" style="width: 100%;">
                    {{ item.raw.label }}
                  </span>
                </template>
              </v-select>
            </v-col>
            <!-- field value -->
            <v-col
              :cols="config.items.fieldValue.cols"
              :class="config.col.class"
              :style="config.items.fieldValue.style"
            >
              <v-select
                v-if="field.fieldOptions"
                :model-value="field.values"
                :items="field.fieldOptions"
                item-title="label"
                item-value="value"
                multiple
                variant="underlined"
                @update:model-value="setFieldValues(field, index, $event)"
              />
              <v-text-field
                v-else-if="field.type === 'string'"
                :model-value="field.value"
                variant="underlined"
                @update:model-value="setFieldValue(field, index, $event)"
              />
              <v-text-field
                v-else-if="field.type === 'number'"
                :model-value="field.value"
                type="number"
                variant="underlined"
                @:model-value="setFieldValue(field, index, $event)"
              />
              <v-checkbox
                v-else-if="field.type === 'boolean'"
                :model-value="field.value"
                @update:model-value="setFieldValue(field, index, $event!)"
              />
              <v-menu
                v-else-if="field.type === 'date'"
                v-model="datePickers[index]"
                :close-on-content-click="false"
                transition="scale-transition"
                offset-y
                max-width="290px"
                min-width="auto"
              >
                <template #activator="{ props }">
                  <v-text-field
                    v-model="field.value"
                    persistent-hint
                    :prepend-icon="$globals.icons.calendar"
                    variant="underlined"
                    color="primary"
                    v-bind="props"
                    readonly
                  />
                </template>
                <v-date-picker
                  :model-value="field.value ? new Date(field.value + 'T00:00:00') : null"
                  hide-header
                  :first-day-of-week="firstDayOfWeek"
                  :local="$i18n.locale"
                  @update:model-value="val => setFieldValue(field, index, val ? val.toISOString().slice(0, 10) : '')"
                />
              </v-menu>
              <RecipeOrganizerSelector
                v-else-if="field.type === Organizer.Category"
                :model-value="field.organizers"
                :selector-type="Organizer.Category"
                :show-add="false"
                :show-label="false"
                :show-icon="false"
                variant="underlined"
                @update:model-value="setOrganizerValues(field, index, $event)"
              />
              <RecipeOrganizerSelector
                v-else-if="field.type === Organizer.Tag"
                :model-value="field.organizers"
                :selector-type="Organizer.Tag"
                :show-add="false"
                :show-label="false"
                :show-icon="false"
                variant="underlined"
                @update:model-value="setOrganizerValues(field, index, $event)"
              />
              <RecipeOrganizerSelector
                v-else-if="field.type === Organizer.Tool"
                :model-value="field.organizers"
                :selector-type="Organizer.Tool"
                :show-add="false"
                :show-label="false"
                :show-icon="false"
                variant="underlined"
                @update:model-value="setOrganizerValues(field, index, $event)"
              />
              <RecipeOrganizerSelector
                v-else-if="field.type === Organizer.Food"
                :model-value="field.organizers"
                :selector-type="Organizer.Food"
                :show-add="false"
                :show-label="false"
                :show-icon="false"
                variant="underlined"
                @update:model-value="setOrganizerValues(field, index, $event)"
              />
              <RecipeOrganizerSelector
                v-else-if="field.type === Organizer.Household"
                :model-value="field.organizers"
                :selector-type="Organizer.Household"
                :show-add="false"
                :show-label="false"
                :show-icon="false"
                variant="underlined"
                @update:model-value="setOrganizerValues(field, index, $event)"
              />
            </v-col>
            <!-- right parenthesis -->
            <v-col
              v-if="showAdvanced"
              :cols="config.items.rightParens.cols"
              :class="config.col.class"
              :style="config.items.rightParens.style"
            >
              <v-select
                :model-value="field.rightParenthesis"
                :items="['', ')', '))', ')))']"
                variant="underlined"
                @update:model-value="setRightParenthesisValue(field, index, $event)"
              >
                <template #chip="{ item }">
                  <span :class="config.select.textClass" style="width: 100%;">
                    {{ item.raw }}
                  </span>
                </template>
              </v-select>
            </v-col>
            <!-- field actions -->
            <v-col
              :cols="config.items.fieldActions.cols"
              :class="config.col.class"
              :style="config.items.fieldActions.style"
            >
              <BaseButtonGroup
                :buttons="[
                  {
                    icon: $globals.icons.delete,
                    text: $t('general.delete'),
                    event: 'delete',
                    disabled: fields.length === 1,
                  },
                ]"
                class="my-auto"
                @delete="removeField(index)"
              />
            </v-col>
          </v-row>
        </VueDraggable>
      </v-container>
    </v-card-text>
    <v-card-actions>
      <v-row fluid class="d-flex justify-end pa-0 mx-2">
        <v-spacer />
        <v-checkbox
          v-model="showAdvanced"
          hide-details
          :label="$t('general.show-advanced')"
          class="my-auto mr-4"
          color="primary"
        />
        <BaseButton
          create
          :text="$t('general.add-field')"
          class="my-auto"
          @click="addField(fieldDefs[0])"
        />
      </v-row>
    </v-card-actions>
  </v-card>
</template>

<script lang="ts">
import { VueDraggable } from "vue-draggable-plus";
import { useDebounceFn } from "@vueuse/core";
import { useHouseholdSelf } from "~/composables/use-households";
import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerSelector.vue";
import { Organizer } from "~/lib/api/types/non-generated";
import type { LogicalOperator, QueryFilterJSON, QueryFilterJSONPart, RelationalKeyword, RelationalOperator } from "~/lib/api/types/response";
import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/store";
import { type Field, type FieldDefinition, type FieldValue, type OrganizerBase, useQueryFilterBuilder } from "~/composables/use-query-filter-builder";

export default defineNuxtComponent({
  components: {
    VueDraggable,
    RecipeOrganizerSelector,
  },
  props: {
    fieldDefs: {
      type: Array as () => FieldDefinition[],
      required: true,
    },
    initialQueryFilter: {
      type: Object as () => QueryFilterJSON | null,
      default: null,
    },
  },
  emits: ["input", "inputJSON"],
  setup(props, context) {
    const { household } = useHouseholdSelf();
    const { logOps, relOps, buildQueryFilterString, getFieldFromFieldDef, isOrganizerType } = useQueryFilterBuilder();

    const firstDayOfWeek = computed(() => {
      return household.value?.preferences?.firstDayOfWeek || 0;
    });

    const state = reactive({
      showAdvanced: false,
      qfValid: false,
      datePickers: [] as boolean[],
      drag: false,
    });

    const storeMap = {
      [Organizer.Category]: useCategoryStore(),
      [Organizer.Tag]: useTagStore(),
      [Organizer.Tool]: useToolStore(),
      [Organizer.Food]: useFoodStore(),
      [Organizer.Household]: useHouseholdStore(),
    };

    function onDragEnd(event: any) {
      state.drag = false;

      const oldIndex: number = event.oldIndex;
      const newIndex: number = event.newIndex;
      state.datePickers[oldIndex] = false;
      state.datePickers[newIndex] = false;
    }

		// add id to fields to prevent reactivity issues
		type FieldWithId = Field & { id: number };
		const fields = ref<FieldWithId[]>([]);

		const uid = ref(1); // init uid to pass to fields
		function useUid() {
		  return uid.value++;
		}
		function addField(field: FieldDefinition) {
		  fields.value.push({
		    ...getFieldFromFieldDef(field),
		    id: useUid(),
		  });
		  state.datePickers.push(false);
		};

		function setField(index: number, fieldLabel: string) {
		  state.datePickers[index] = false;
		  const fieldDef = props.fieldDefs.find(fieldDef => fieldDef.label === fieldLabel);
		  if (!fieldDef) {
		    return;
		  }

		  const resetValue = (fieldDef.type !== fields.value[index].type) || (fieldDef.fieldOptions !== fields.value[index].fieldOptions);
		  const updatedField = { ...fields.value[index], ...fieldDef };

		  // we have to set this explicitly since it might be undefined
		  updatedField.fieldOptions = fieldDef.fieldOptions;

		  fields.value[index] = {
		    ...getFieldFromFieldDef(updatedField, resetValue),
		    id: fields.value[index].id, // keep the id
		  };
		}

		function setLeftParenthesisValue(field: FieldWithId, index: number, value: string) {
		  fields.value[index].leftParenthesis = value;
		}

		function setRightParenthesisValue(field: FieldWithId, index: number, value: string) {
		  fields.value[index].rightParenthesis = value;
		}

		function setLogicalOperatorValue(field: FieldWithId, index: number, value: LogicalOperator | undefined) {
		  if (!value) {
		    value = logOps.value.AND.value;
		  }

		  fields.value[index].logicalOperator = value ? logOps.value[value] : undefined;
		}

		function setRelationalOperatorValue(field: FieldWithId, index: number, value: RelationalKeyword | RelationalOperator) {
		  fields.value[index].relationalOperatorValue = relOps.value[value];
		}

		function setFieldValue(field: FieldWithId, index: number, value: FieldValue) {
		  state.datePickers[index] = false;
		  fields.value[index].value = value;
		}

		function setFieldValues(field: FieldWithId, index: number, values: FieldValue[]) {
		  fields.value[index].values = values;
		}

		function setOrganizerValues(field: FieldWithId, index: number, values: OrganizerBase[]) {
		  setFieldValues(field, index, values.map(value => value.id.toString()));
		  fields.value[index].organizers = values;
		}

		function removeField(index: number) {
		  fields.value.splice(index, 1);
		  state.datePickers.splice(index, 1);
		};

		const fieldsUpdater = useDebounceFn((/* newFields: typeof fields.value */) => {
		  /* newFields.forEach((field, index) => {
				const updatedField = getFieldFromFieldDef(field);
				fields.value[index] = updatedField; // recursive!!!
			}); */

		  const qf = buildQueryFilterString(fields.value, state.showAdvanced);
		  if (qf) {
		    console.debug(`Set query filter: ${qf}`);
		  }
		  state.qfValid = !!qf;

		  context.emit("input", qf || undefined);
		  context.emit("inputJSON", qf ? buildQueryFilterJSON() : undefined);
		}, 500);

		watch(fields, fieldsUpdater, { deep: true });

		async function hydrateOrganizers(field: FieldWithId, index: number) {
		  if (!field.values?.length || !isOrganizerType(field.type)) {
		    return;
		  }

		  field.organizers = [];

		  const { store, actions } = storeMap[field.type];
		  if (!store.value.length) {
		    await actions.refresh();
		  }

		  const organizers = field.values.map((value) => {
		    const organizer = store.value.find(item => item?.id?.toString() === value);
		    if (!organizer) {
		      console.error(`Could not find organizer with id ${value}`);
		      return undefined;
		    }
		    return organizer;
		  });
		  field.organizers = organizers.filter(organizer => organizer !== undefined) as OrganizerBase[];
		  setOrganizerValues(field, index, field.organizers);
		}

		function initFieldsError(error = "") {
		  if (error) {
		    console.error(error);
		  }

		  fields.value = [];
		  if (props.fieldDefs.length) {
		    addField(props.fieldDefs[0]);
		  }
		}

		function initializeFields() {
		  if (!props.initialQueryFilter?.parts?.length) {
		    return initFieldsError();
		  };

		  const initFields: FieldWithId[] = [];
		  let error = false;
		  props.initialQueryFilter.parts.forEach((part: QueryFilterJSONPart, index: number) => {
		    const fieldDef = props.fieldDefs.find(fieldDef => fieldDef.name === part.attributeName);
		    if (!fieldDef) {
		      error = true;
		      return initFieldsError(`Invalid query filter; unknown attribute name "${part.attributeName || ""}"`);
		    }

		    const field: FieldWithId = {
		      ...getFieldFromFieldDef(fieldDef),
		      id: useUid(),
		    };
		    field.leftParenthesis = part.leftParenthesis || field.leftParenthesis;
		    field.rightParenthesis = part.rightParenthesis || field.rightParenthesis;
		    field.logicalOperator = part.logicalOperator
		      ? logOps.value[part.logicalOperator]
		      : field.logicalOperator;
		    field.relationalOperatorValue = part.relationalOperator
		      ? relOps.value[part.relationalOperator]
		      : field.relationalOperatorValue;

		    if (field.leftParenthesis || field.rightParenthesis) {
		      state.showAdvanced = true;
		    }

		    if (field.fieldOptions?.length || isOrganizerType(field.type)) {
		      if (typeof part.value === "string") {
		        field.values = part.value ? [part.value] : [];
		      }
		      else {
		        field.values = part.value || [];
		      }

		      if (isOrganizerType(field.type)) {
		        hydrateOrganizers(field, index);
		      }
		    }
		    else if (field.type === "boolean") {
		      const boolString = part.value || "false";
		      field.value = (
		        boolString[0].toLowerCase() === "t"
		        || boolString[0].toLowerCase() === "y"
		        || boolString[0] === "1"
		      );
		    }
		    else if (field.type === "number") {
		      field.value = Number(part.value as string || "0");
		      if (isNaN(field.value)) {
		        error = true;
		        return initFieldsError(`Invalid query filter; invalid number value "${(part.value || "").toString()}"`);
		      }
		    }
		    else if (field.type === "date") {
		      field.value = part.value as string || "";
		      const date = new Date(field.value);
		      if (isNaN(date.getTime())) {
		        error = true;
		        return initFieldsError(`Invalid query filter; invalid date value "${(part.value || "").toString()}"`);
		      }
		    }
		    else {
		      field.value = part.value as string || "";
		    }

		    initFields.push(field);
		  });

		  if (initFields.length && !error) {
		    fields.value = initFields;
		  }
		  else {
		    initFieldsError();
		  }
		};

		try {
		  initializeFields();
		}
		catch (error) {
		  initFieldsError(`Error initializing fields: ${(error || "").toString()}`);
		}

		function buildQueryFilterJSON(): QueryFilterJSON {
		  const parts = fields.value.map((field) => {
		    const part: QueryFilterJSONPart = {
		      attributeName: field.name,
		      leftParenthesis: field.leftParenthesis,
		      rightParenthesis: field.rightParenthesis,
		      logicalOperator: field.logicalOperator?.value,
		      relationalOperator: field.relationalOperatorValue?.value,
		    };

		    if (field.fieldOptions?.length || isOrganizerType(field.type)) {
		      part.value = field.values.map(value => value.toString());
		    }
		    else if (field.type === "boolean") {
		      part.value = field.value ? "true" : "false";
		    }
		    else {
		      part.value = (field.value || "").toString();
		    }

		    return part;
		  });

		  const qfJSON = { parts } as QueryFilterJSON;
		  console.debug(`Built query filter JSON: ${JSON.stringify(qfJSON)}`);
		  return qfJSON;
		}

		const config = computed(() => {
		  const baseColMaxWidth = 55;
		  return {
		    col: {
		      class: "d-flex justify-center align-end field-col pa-1",
		    },
		    select: {
		      textClass: "d-flex justify-center text-center",
		    },
		    items: {
		      icon: {
		        cols: 1,
		        style: "width: fit-content;",
		      },
		      leftParens: {
		        cols: state.showAdvanced ? 1 : 0,
		        style: `min-width: ${state.showAdvanced ? baseColMaxWidth : 0}px;`,
		      },
		      logicalOperator: {
		        cols: 1,
		        style: `min-width: ${baseColMaxWidth}px;`,
		      },
		      fieldName: {
		        cols: state.showAdvanced ? 2 : 3,
		        style: `min-width: ${state.showAdvanced ? baseColMaxWidth * 2 : baseColMaxWidth * 3}px;`,
		      },
		      relationalOperator: {
		        cols: 2,
		        style: `min-width: ${baseColMaxWidth * 2}px;`,
		      },
		      fieldValue: {
		        cols: state.showAdvanced ? 3 : 4,
		        style: `min-width: ${state.showAdvanced ? baseColMaxWidth * 2 : baseColMaxWidth * 3}px;`,
		      },
		      rightParens: {
		        cols: state.showAdvanced ? 1 : 0,
		        style: `min-width: ${state.showAdvanced ? baseColMaxWidth : 0}px;`,
		      },
		      fieldActions: {
		        cols: 1,
		        style: `min-width: ${baseColMaxWidth}px;`,
		      },
		    },
		  };
		});

		return {
		  Organizer,
		  ...toRefs(state),
		  logOps,
		  relOps,
		  config,
		  firstDayOfWeek,
		  onDragEnd,
		  // Fields
		  fields,
		  addField,
		  setField,
		  setLeftParenthesisValue,
		  setRightParenthesisValue,
		  setLogicalOperatorValue,
		  setRelationalOperatorValue,
		  setFieldValue,
		  setFieldValues,
		  setOrganizerValues,
		  removeField,
		};
  },
});
</script>

<style scoped>
* {
  font-size: 1em;
}
</style>
