<template>
  <label v-if="field.label" :for="field.name || undefined">
    {{ field.label }}
  </label>
  <label
    v-else-if="field.label !== false && field.name"
    :for="field.name || undefined"
  >
    {{ field.name }}
  </label>
  <div class="component">
    <template v-if="field.type === 'sub'">
      <component
        :is="props.formComponent || AutoForm"
        @update:modelValue="handleUpdate($event)"
        :modelValue="object"
        :fields="field.fields"
        :errors="errors"
        :sub="true"
        :class="field.css_classes"
      />
    </template>
    <div v-else-if="field.type === 'inline'" class="inline-form">
      <Card v-for="row in currentValue">
        <template #content>
          <div class="row">
            <component
              :is="props.formComponent || AutoForm"
              :key="row.id"
              :modelValue="row"
              :defaults="defaults"
              :errors="errors"
              :fields="field.fields"
              sub
              @update:modelValue="handleInlineUpdate(row.id, $event)"
            />
            <Button @click="handleInlineRemove(row.id)" icon="pi pi-times" />
            <!-- :label="`Remove ${row.id}`"  -->
          </div>
        </template>
      </Card>
      <Card v-if="!field.inlineDisableAdd" class="add-another">
        <template #content>
          <component
            :is="props.formComponent"
            :key="'new'"
            :modelValue="{}"
            :defaults="defaults"
            :errors="errors"
            :fields="field.fields"
            sub
            @update:modelValue="handleInlineUpdate(undefined, $event)"
          />
        </template>
      </Card>
    </div>
    <Dropdown
      v-else-if="(!field.type && field.options) || field.type === 'select'"
      @update:modelValue="update($event)"
      @focus="field.focus"
      :required="field.required === undefined ? true : field.required"
      :readonly="field.readonly"
      :tabindex="field.tabindex"
      :class="field.css_classes || 'form-control'"
      :options="field.options"
      :modelValue="currentValue"
      :invalid="!!errorValue"
      :placeholder="field.placeholder"
      :aria-label="field.ariaLabel"
      :disabled="field.disabled"
      style="position: relative"
      optionLabel="name"
      optionValue="value"
    />
    <div
      v-else-if="field.name && field.type === 'checkbox-list'"
      class="checkbox-list"
    >
      <label v-for="option of field.options" :key="option.value">
        <Checkbox
          @update:model-value="update($event, option.value)"
          :value="option.value"
          :readonly="field.readonly"
          :checked="
            currentValue &&
            (currentValue.indexOf(option.value) > -1 ||
              currentValue[option.value])
          "
          :invalid="!!errorValue"
          :aria-label="field.ariaLabel"
          :disabled="field.disabled"
        />
        {{ option.name || option.label }}
      </label>
    </div>
    <div v-else-if="field.name && field.type === 'color'" class="color-row">
      <ColorPicker
        @update:modelValue="update($event)"
        :modelValue="currentValue"
        :name="field.name"
        :placeholder="field.placeholder"
        :readonly="field.readonly"
        :tabindex="field.tabindex ? field.tabindex.toString() : undefined"
        :required="field.required === undefined ? true : field.required"
        :class="field.css_classes || 'form-control'"
      />
      <InputText
        @update:modelValue="update($event)"
        :modelValue="currentValue"
        :invalid="!!errorValue"
        :style="{ width: '6em' }"
        :name="field.name"
        :placeholder="field.placeholder"
        :readonly="field.readonly"
        :tabindex="field.tabindex"
        :required="field.required === undefined ? true : field.required"
        :class="field.css_classes || 'form-control'"
        :aria-label="field.ariaLabel"
        :disabled="field.disabled"
        type="text"
        maxlength="6"
      />
    </div>
    <div v-else-if="field.name && field.type === 'checkbox'" class="checkbox">
      <Checkbox
        :modelValue="currentValue"
        :binary="true"
        :readonly="field.readonly"
        :invalid="!!errorValue"
        :aria-label="field.ariaLabel"
        :disabled="field.disabled"
        @update:modelValue="update($event)"
      />
    </div>
    <Textarea
      v-else-if="field.name && field.type === 'textarea'"
      @update:model-value="update($event)"
      :modelValue="currentValue"
      :rows="field.rows || 3"
      :cols="field.cols || 23"
      :name="field.name"
      :placeholder="field.placeholder"
      :readonly="field.readonly"
      :tabindex="field.tabindex"
      :required="field.required === undefined ? true : field.required"
      :class="field.css_classes || 'form-control'"
      :invalid="!!errorValue"
      :aria-label="field.ariaLabel"
      :disabled="field.disabled"
    />
    <div v-else-if="field.name && field.type === 'range'">
      <InputNumber
        @update:modelValue="update($event)"
        :modelValue="currentValue"
        :name="field.name"
        :placeholder="field.placeholder"
        :readonly="field.readonly"
        :tabindex="field.tabindex"
        :min="field.min"
        :max="field.max"
        :step="field.step"
        :required="field.required === undefined ? true : field.required"
        :class="field.css_classes || 'form-control'"
        :invalid="!!errorValue"
        :aria-label="field.ariaLabel"
        :disabled="field.disabled"
      />
      <Slider
        @update:modelValue="update($event)"
        :modelValue="parseFloat(currentValue)"
        :name="field.name"
        :placeholder="field.placeholder"
        :readonly="field.readonly"
        :tabindex="field.tabindex"
        :min="field.min"
        :max="field.max"
        :step="field.step"
        :required="field.required === undefined ? true : field.required"
        :class="field.css_classes || 'form-control'"
        :invalid="!!errorValue"
        :aria-label="field.ariaLabel"
      />
    </div>
    <InputNumber
      v-else-if="field.type === 'number'"
      @update:modelValue="update($event)"
      :modelValue="currentValue"
      :size="field.size"
      :name="field.name"
      :placeholder="field.placeholder"
      :readonly="field.readonly"
      :tabindex="field.tabindex"
      :min="field.min"
      :max="field.max"
      :step="field.step"
      :required="field.required === undefined ? true : field.required"
      :class="field.css_classes || 'form-control'"
      :invalid="!!errorValue"
      :aria-label="field.ariaLabel"
      :disabled="field.disabled"
    />
    <InputText
      v-else-if="field.name"
      @update:model-value="update($event)"
      :modelValue="currentValue"
      :type="field.type"
      :name="field.name"
      :autocomplete="field.autocomplete"
      :placeholder="field.placeholder"
      :readonly="field.readonly"
      :tabindex="field.tabindex"
      :maxlength="field.maxlength"
      :pattern="field.pattern"
      :required="field.required === undefined ? true : field.required"
      :class="field.css_classes || 'form-control'"
      :invalid="!!errorValue"
      :aria-label="field.ariaLabel"
      :disabled="field.disabled"
    />
    <div v-if="field.help" class="help">{{ field.help }}</div>
    <ErrorList v-if="field.name" :errors="dottedGet(errors, field.name)" />
  </div>
</template>

<script setup lang="ts">
import Button from "primevue/button";
import Card from "primevue/card";
import Checkbox from "primevue/checkbox";
import ColorPicker from "primevue/colorpicker";
import Dropdown from "primevue/dropdown";
import InputNumber from "primevue/inputnumber";
import InputText from "primevue/inputtext";
import Slider from "primevue/slider";
import Textarea from "primevue/textarea";
import type { Component } from "vue";
import { computed } from "vue";
import copy from "../../functions/copy";
import { dottedGet, dottedSet } from "../../functions/dotted_get.ts";
import { ErrorDict, FieldDef, updateModelValue } from "./defs";
import ErrorList from "./ErrorList.vue";
import AutoForm from "./AutoForm.vue";

defineOptions({ inheritAttrs: false });
const emit = defineEmits(["update:modelValue"]);

const props = defineProps<{
  field: FieldDef;
  modelValue: any;
  formComponent?: Component;
  errors?: ErrorDict;
  defaults?: any;
}>();

const errorValue = computed(() => {
  if (props.field.name) {
    return dottedGet(props.errors, props.field.name);
  }
});

const object = computed(() => {
  const retval = copy(props.modelValue);
  let value = undefined;
  if (props.field.name) {
    value = dottedGet(retval, props.field.name);

    // if the value isn't an option, remove it
    if (props.field.options && value) {
      if (!props.field.options.find((o) => o.value === value)) {
        dottedSet(retval, props.field.name, undefined);
      }
    }

    // match defaults
    if (!value && props.field.default) {
      dottedSet(retval, props.field.name, props.field.default);
    }
  }
  if (props.defaults) {
    addFallbacks(retval, props.defaults);
  }
  return retval;
});

const currentValue = computed(() => {
  let retval = undefined;
  try {
    if (props.field.name) {
      retval = dottedGet(object.value, props.field.name);
    }
  } catch (ex) {
    console.error("ex", ex);
  }
  return retval;
});

function addFallbacks(retval: any, defaults: any) {
  for (const key in defaults) {
    const value = defaults[key];
    if (typeof value === "object") {
      retval[key] = addFallbacks(retval[key] ?? {}, value);
    } else if (retval[key] === undefined) {
      retval[key] = value;
    }
  }
  return retval;
}

function handleInlineUpdate(id: string | undefined, data: any) {
  if (props.field.name) {
    const modelValue = copy(props.modelValue);
    let value: { id: number | string }[] = dottedGet(
      modelValue,
      props.field.name,
    );
    if (!value) {
      value = [];
    }
    if (!id) {
      value.push(data);
    } else {
      const idx = value.findIndex((r) => r.id === id);
      value[idx] = data;
    }
    emit("update:modelValue", modelValue);
  }
}

function handleInlineRemove(id: string) {
  if (props.field.name) {
    const modelValue = copy(props.modelValue);
    let value: { id: number | string }[] = dottedGet(
      modelValue,
      props.field.name,
    );
    if (!value) {
      value = [];
    }
    const idx = value.findIndex((r) => r.id === id);
    value.splice(idx, 1);
    emit("update:modelValue", modelValue);
  }
}

function handleUpdate(values: any) {
  emit("update:modelValue", values);
}

function update(value: any, key?: string) {
  if (value != props.modelValue) {
    emit(
      "update:modelValue",
      updateModelValue(props.modelValue, props.field, value, key),
    );
  }
}
</script>

<style scoped>
.p-colorpicker {
  padding: 5px;
}
.p-slider {
  margin-top: 15px;
  margin-left: 15px;
}
.checkbox {
  margin-top: 15px;
  margin-left: 5px;
}
.row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: flex-start;
}
</style>
