<template>
  <div class="natural-date-input">
    <UInput
      :model-value="rawInput"
      :placeholder="placeholder"
      :color="trailingState"
      @update:model-value="onInputChange"
    >
      <template #trailing>
        <Icon
          v-if="isValid && rawInput.trim()"
          name="heroicons:check-circle"
          class="w-5 h-5"
          style="color: var(--candle)"
        />
        <Icon
          v-else-if="hasError && rawInput.trim()"
          name="heroicons:exclamation-circle"
          class="w-5 h-5"
          style="color: var(--ember)"
        />
      </template>
    </UInput>
    <p
      v-if="rawInput.trim() && isValid"
      class="preview-line"
      style="color: var(--candle)"
    >
      &rarr; {{ previewText }}
    </p>
    <p
      v-else-if="rawInput.trim() && hasError"
      class="preview-line"
      style="color: var(--ember)"
    >
      {{ errorMessage }}
    </p>
  </div>
</template>

<script setup>
import * as chrono from "chrono-node";

const props = defineProps({
  modelValue: { type: String, default: "" },
  placeholder: {
    type: String,
    default: 'e.g., "tomorrow at 3pm", "Nov 22 at 3pm"',
  },
  displayTimezone: { type: String, default: "" },
  required: { type: Boolean, default: false },
});

const emit = defineEmits(["update:modelValue"]);

const rawInput = ref("");
const isValid = ref(false);
const hasError = ref(false);
const errorMessage = ref("");
// previewDate holds the parsed value as a UTC Date so we can format it in
// arbitrary timezones without re-parsing. Source of truth for the preview.
const previewDate = ref(null);

const trailingState = computed(() => {
  if (!rawInput.value.trim()) return undefined;
  if (hasError.value) return "error";
  if (isValid.value) return "success";
  return undefined;
});

const previewText = computed(() => {
  if (!previewDate.value) return "";
  const tz = activeTZ();
  const date = new Intl.DateTimeFormat("en-US", {
    timeZone: tz,
    weekday: "short",
    month: "short",
    day: "numeric",
    year: "numeric",
    hour: "numeric",
    minute: "2-digit",
    hour12: true,
  }).format(previewDate.value);
  const abbr = shortTimezoneName(previewDate.value, tz);
  return abbr ? `${date} ${abbr}` : date;
});

const activeTZ = () =>
  props.displayTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;

// Seed the input from modelValue without triggering chrono. The parent's
// value is canonical — we just render it as a chrono-friendly readable
// string so the user can backspace and tweak in place.
const seedFromModelValue = () => {
  if (!props.modelValue) {
    rawInput.value = "";
    isValid.value = false;
    hasError.value = false;
    errorMessage.value = "";
    previewDate.value = null;
    return;
  }
  const tz = activeTZ();
  const utc = zonedLocalToUTC(props.modelValue, tz);
  if (!utc) return;
  previewDate.value = utc;
  isValid.value = true;
  hasError.value = false;
  errorMessage.value = "";
  rawInput.value = readableSeed(utc, tz);
};

onMounted(seedFromModelValue);

watch(
  () => props.modelValue,
  (next) => {
    const tz = activeTZ();
    const expected = previewDate.value
      ? utcToZonedLocal(previewDate.value, tz)
      : "";
    if (next === expected) return;
    seedFromModelValue();
  },
);

watch(
  () => props.displayTimezone,
  () => {
    // Re-interpret the current input under the new TZ so the preview and
    // emitted value reflect the new timezone semantics.
    if (rawInput.value.trim()) parse(rawInput.value);
  },
);

const onInputChange = (value) => {
  rawInput.value = value;
  parse(value);
};

const parse = (input) => {
  const trimmed = input.trim();
  if (!trimmed) {
    isValid.value = false;
    hasError.value = false;
    errorMessage.value = "";
    previewDate.value = null;
    emit("update:modelValue", "");
    return;
  }
  const tz = activeTZ();
  let results;
  try {
    results = chrono.parse(trimmed, referenceNowInTZ(tz));
  } catch {
    setError("Couldn't read that — try \"tomorrow 3pm\" or \"Nov 22 3pm\"");
    return;
  }
  if (!results.length) {
    setError("Couldn't read that — try \"tomorrow 3pm\" or \"Nov 22 3pm\"");
    return;
  }
  const date = results[0].date();
  if (!date || Number.isNaN(date.getTime())) {
    setError("Couldn't read that date");
    return;
  }
  // chrono returned a Date whose browser-local components match what the
  // user typed in the event timezone (because we shifted the reference).
  // Read those components as wall-clock in displayTimezone.
  const localStr = browserComponentsToString(date);
  const utc = zonedLocalToUTC(localStr, tz);
  if (!utc) {
    setError("Couldn't parse this date");
    return;
  }
  isValid.value = true;
  hasError.value = false;
  errorMessage.value = "";
  previewDate.value = utc;
  emit("update:modelValue", localStr);
};

const setError = (msg) => {
  isValid.value = false;
  hasError.value = true;
  errorMessage.value = msg;
  previewDate.value = null;
  emit("update:modelValue", "");
};

// Build a Date object whose browser-local components equal the current
// wall-clock in the given IANA timezone, so chrono's "tomorrow"/"next
// Friday" anchor to the event TZ rather than the editor's browser TZ.
const referenceNowInTZ = (tz) => {
  const nowStr = utcToZonedLocal(new Date(), tz);
  if (!nowStr) return new Date();
  const [d, t] = nowStr.split("T");
  const [y, mo, day] = d.split("-").map(Number);
  const [h, mi] = t.split(":").map(Number);
  return new Date(y, mo - 1, day, h, mi);
};

const browserComponentsToString = (date) => {
  const y = date.getFullYear();
  const mo = String(date.getMonth() + 1).padStart(2, "0");
  const d = String(date.getDate()).padStart(2, "0");
  const h = String(date.getHours()).padStart(2, "0");
  const mi = String(date.getMinutes()).padStart(2, "0");
  return `${y}-${mo}-${d}T${h}:${mi}`;
};

const readableSeed = (utc, tz) => {
  // Format chosen to round-trip cleanly through chrono.parse.
  return new Intl.DateTimeFormat("en-US", {
    timeZone: tz,
    month: "short",
    day: "numeric",
    year: "numeric",
    hour: "numeric",
    minute: "2-digit",
    hour12: true,
  }).format(utc);
};
</script>

<style scoped>
.natural-date-input {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.preview-line {
  font-size: 12px;
  margin: 0;
}
</style>
