import { useEffect, useMemo, useRef, useState } from "react";
import { Group, NumberInput, Select, Stack } from "@mantine/core";

export type DurationUnit = "minutes" | "hours" | "days";

const UNIT_MINUTES: Record<DurationUnit, number> = {
  minutes: 1,
  hours: 60,
  days: 1440,
};

const UNIT_OPTIONS: { value: DurationUnit; label: string }[] = [
  { value: "minutes", label: "min" },
  { value: "hours", label: "hr" },
  { value: "days", label: "day" },
];

/** Convert a value expressed in `from` units to `to` units. */
function convert(value: number, from: DurationUnit, to: DurationUnit): number {
  return (value * UNIT_MINUTES[from]) / UNIT_MINUTES[to];
}

/** Pick the largest unit that keeps a value a clean-ish whole number, so a
 * stored 0.0667h loads back as "4 min" rather than "0.0667 hr". */
function bestDisplayUnit(minutes: number): DurationUnit {
  if (minutes <= 0) return "minutes";
  if (minutes % 1440 === 0) return "days";
  if (minutes % 60 === 0) return "hours";
  return "minutes";
}

export interface DurationFieldProps {
  label: string;
  description?: string;
  /** Current value, expressed in `nativeUnit` (what the API/DB stores). */
  value: number | string;
  /** The unit the parent stores/sends. The field converts to this on change. */
  nativeUnit: DurationUnit;
  /** Called with the value converted back to `nativeUnit` (or "" when blank). */
  onChange: (nativeValue: number | "") => void;
  /** Smallest allowed value, in `nativeUnit`. */
  min?: number;
  disabled?: boolean;
}

export default function DurationField({
  label,
  description,
  value,
  nativeUnit,
  onChange,
  min,
  disabled,
}: DurationFieldProps) {
  const nativeMinutes = useMemo(() => {
    const num = value === "" || value == null ? NaN : Number(value);
    return Number.isFinite(num) ? num * UNIT_MINUTES[nativeUnit] : NaN;
  }, [value, nativeUnit]);

  // Display unit is user-driven; seed it from the incoming value once.
  const [unit, setUnit] = useState<DurationUnit>(() =>
    Number.isFinite(nativeMinutes) ? bestDisplayUnit(nativeMinutes) : nativeUnit,
  );

  // The value usually arrives async (after the initial "" render), so the
  // useState seed above runs before it exists. Re-pick the friendliest display
  // unit the first time a real value shows up — but never again, so the user's
  // manual unit choice sticks.
  const seeded = useRef(false);
  useEffect(() => {
    if (!seeded.current && Number.isFinite(nativeMinutes)) {
      seeded.current = true;
      setUnit(bestDisplayUnit(nativeMinutes));
    }
  }, [nativeMinutes]);

  const displayValue: number | "" = Number.isFinite(nativeMinutes)
    ? Number(convert(nativeMinutes, "minutes", unit).toFixed(4))
    : "";

  const emitNative = (display: number | "", displayUnit: DurationUnit) => {
    if (display === "" || !Number.isFinite(Number(display))) {
      onChange("");
      return;
    }
    const native = convert(Number(display), displayUnit, nativeUnit);
    onChange(Number(native.toFixed(6)));
  };

  return (
    <Stack gap={4}>
      <Group gap="xs" align="flex-end" wrap="nowrap">
        <NumberInput
          label={label}
          description={description}
          value={displayValue}
          onChange={(v) =>
            emitNative(v === "" ? "" : Number(v), unit)
          }
          clampBehavior="none"
          allowNegative={false}
          allowDecimal
          min={min != null ? convert(min, nativeUnit, unit) : 0}
          disabled={disabled}
          style={{ flex: 1 }}
        />
        <Select
          aria-label={`${label} unit`}
          data={UNIT_OPTIONS}
          value={unit}
          onChange={(next) => {
            if (!next) return;
            // Only the display unit changes; the stored native value stays put.
            // displayValue re-derives from it on the next render.
            setUnit(next as DurationUnit);
          }}
          allowDeselect={false}
          disabled={disabled}
          w={90}
        />
      </Group>
    </Stack>
  );
}
