// 12-hour time picker for the executive-meetings editor.
//
// Renders THREE controls per field, per the task spec:
//   1. Hour input (1–12, but also accepts a full time as a power-user
//      shortcut: "13:30", "9:30 PM", "0930", "٠٩:٣٠").
//   2. Minute input (00–59).
//   3. AM/PM segmented toggle (no implicit default — opening an
//      empty field seeds the toggle to null, and saving with a
//      1–12 typed hour but no toggle pick surfaces an "ambiguous"
//      parse error to the parent rather than silently committing
//      AM).
//
// Storage stays canonical "HH:mm" 24h; this component is purely a UI
// shell around `combineSplit` from `@/lib/time-12h`.
//
// Two consumers:
//   1) The Manage-tab edit form: the picker is uncontrolled aside
//      from `value` / `onChange` (clean canonical values flow up on
//      blur and on toggle change; ambiguous drafts stay inside the
//      picker until the user resolves them, and the dialog's Save
//      button calls `commit()` to validate before persisting).
//   2) The inline schedule time-cell editor (TimeRangeCell): the
//      parent reads the latest draft via the imperative `commit()`
//      handle when the user presses Enter or blurs the wrapper, so
//      a save that races React state batching still sees the right
// value (matches the belt-and-braces pattern set up
//      against the prior text input).

import {
  forwardRef,
  useCallback,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
  type Ref,
  type KeyboardEvent as ReactKeyboardEvent,
} from "react";
import {
  combineSplit,
  periodFromCanonical,
  splitCanonicalForPicker,
  type CombineResult,
  type Period,
} from "@/lib/time-12h";

export type TimePicker12hHandle = {
  /**
   * Compute the canonical "HH:mm" 24h string from the picker's current
   * draft (typed hour + minute + toggle) WITHOUT waiting for an
   * onChange round-trip. Used by the inline schedule editor's save
   * path so a save triggered by Enter or wrapper-blur reads the live
   * value the user sees in the inputs, and by the Manage form's
   * Save button to validate before persisting.
   */
  commit(): CombineResult;
  /** Move keyboard focus into the hour input. */
  focus(): void;
  /** Select the existing text inside the hour input. */
  select(): void;
};

export type TimePicker12hSize = "inline" | "form";

type Props = {
  /** Canonical "HH:mm" 24h string, or "" for empty. */
  value: string;
  /**
   * Fires when the user lands on a clean canonical value: any of the
   * sub-controls blurred with valid input, AM/PM toggled with valid
   * input, or cleared. Ambiguous / invalid mid-edit drafts are NOT
   * propagated — the parent learns about those via `commit()` from
   * the imperative handle.
   */
  onChange?: (canonical: string) => void;
  /** Enter pressed inside any sub-control. */
  onCommit?: () => void;
  /** Escape pressed inside any sub-control. */
  onCancel?: () => void;
  /** Stable id for the hour input — paired with a wrapping
   *  <label htmlFor=…> in the inline editor. */
  inputId?: string;
  /** Test id for the HOUR input. The inline editor passes the legacy
   *  `em-time-start-${id}` / `em-time-end-${id}` ids so the existing
   *  keyboard-editing e2e suite still resolves them. */
  inputTestId?: string;
  /** Test id for the MINUTE input. Defaults to `${inputTestId}-minute`. */
  minuteTestId?: string;
  amTestId?: string;
  pmTestId?: string;
  /** Aria-label for the HOUR input. */
  ariaLabel?: string;
  /** Aria-label for the MINUTE input. Defaults to `${ariaLabel} (minutes)`. */
  minuteAriaLabel?: string;
  /** Used as the radiogroup's accessible name. */
  groupLabel?: string;
  amLabel: string;
  pmLabel: string;
  /** Localized "HH" / "MM" placeholders so the AR layout shows
   *  "س س" / "د د" (or whatever the locale prefers). Defaults to
   *  "HH"/"MM". */
  hourPlaceholder?: string;
  minutePlaceholder?: string;
  disabled?: boolean;
  /** Auto-focus the hour input on mount (used by the inline editor's
   *  enter-edit transition). */
  autoFocus?: boolean;
  /** Visual size: `inline` matches the compact schedule cell;
   *  `form` matches the manage-tab form's text inputs. */
  size?: TimePicker12hSize;
};

function TimePicker12hImpl(
  props: Props,
  ref: Ref<TimePicker12hHandle>,
) {
  const {
    value,
    onChange,
    onCommit,
    onCancel,
    inputId,
    inputTestId,
    minuteTestId,
    amTestId,
    pmTestId,
    ariaLabel,
    minuteAriaLabel,
    groupLabel,
    amLabel,
    pmLabel,
    hourPlaceholder = "HH",
    minutePlaceholder = "MM",
    disabled,
    autoFocus,
    size = "form",
  } = props;

  const hourRef = useRef<HTMLInputElement | null>(null);
  const minuteRef = useRef<HTMLInputElement | null>(null);
  const seed = splitCanonicalForPicker(value);
  const [hour, setHour] = useState<string>(() => seed.hour);
  const [minute, setMinute] = useState<string>(() => seed.minute);
  const [period, setPeriod] = useState<Period>(() => periodFromCanonical(value));
  // Tracks whether the user has touched the picker; if so, we don't
  // overwrite their draft when the parent re-renders with a stale
  // `value` prop (e.g. mid-edit re-render in the inline editor before
  // the controlled state catches up).
  const dirtyRef = useRef(false);
  // Mirror of `period` for synchronous reads inside commit() — React
  // state isn't yet updated when commit() runs from the same event
  // tick that toggled the period (e.g. Enter on AM button = "set AM
  // and save").
  const periodRef = useRef<Period>(period);
  periodRef.current = period;

  // Re-sync from `value` when the canonical prop changes AND the user
  // hasn't started editing yet. Manage-form initialiser may populate
  // state asynchronously; the inline editor remounts pickers fresh
  // when entering edit mode so this branch primarily covers the
  // form-edit case.
  useEffect(() => {
    if (dirtyRef.current) return;
    const next = splitCanonicalForPicker(value);
    setHour(next.hour);
    setMinute(next.minute);
    const nextPeriod = periodFromCanonical(value);
    setPeriod(nextPeriod);
    periodRef.current = nextPeriod;
  }, [value]);

  useEffect(() => {
    if (autoFocus) {
      hourRef.current?.focus();
      // Select existing text so typing replaces it — matches the
      // legacy <input type="time"> spinner behaviour the inline
      // editor relied on.
      hourRef.current?.select();
    }
    // Mount-only on purpose: re-running this on prop changes would
    // steal focus from the user mid-edit.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useImperativeHandle(
    ref,
    (): TimePicker12hHandle => ({
      commit: () =>
        combineSplit(
          hourRef.current?.value ?? hour,
          minuteRef.current?.value ?? minute,
          periodRef.current,
        ),
      focus: () => {
        hourRef.current?.focus();
      },
      select: () => {
        hourRef.current?.select();
      },
    }),
    [hour, minute],
  );

  const propagateIfClean = useCallback(
    (nextHour: string, nextMinute: string, nextPeriod: Period) => {
      const result = combineSplit(nextHour, nextMinute, nextPeriod);
      // Only propagate clean values upward; ambiguous / invalid
      // drafts surface via commit() so the parent decides when to
      // toast.
      if (result.error === null) {
        onChange?.(result.value ?? "");
      }
    },
    [onChange],
  );

  const handleHourChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    dirtyRef.current = true;
    setHour(e.target.value);
  };

  const handleMinuteChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    dirtyRef.current = true;
    setMinute(e.target.value);
  };

  const handleHourBlur = () => {
    propagateIfClean(hour, minute, period);
  };

  const handleMinuteBlur = () => {
    propagateIfClean(hour, minute, period);
  };

  const handleInputKeyDown = (e: ReactKeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Escape") {
      e.preventDefault();
      onCancel?.();
    } else if (e.key === "Enter") {
      e.preventDefault();
      onCommit?.();
    }
  };

  const setPeriodAndPropagate = (next: Period) => {
    dirtyRef.current = true;
    periodRef.current = next;
    setPeriod(next);
    propagateIfClean(hour, minute, next);
  };

  const handleButtonKeyDown = (
    e: ReactKeyboardEvent<HTMLButtonElement>,
    p: "am" | "pm",
  ) => {
    if (e.key === "Enter") {
      // Enter on the toggle means "I want this period AND save". We
      // set the period synchronously through the ref so the commit
      // path the parent kicks off in onCommit reads the new value
      // even though React's state hasn't flushed yet.
      e.preventDefault();
      setPeriodAndPropagate(p);
      onCommit?.();
    } else if (e.key === "Escape") {
      e.preventDefault();
      onCancel?.();
    }
    // Space falls through to the default <button> activation, which
    // fires onClick → setPeriodAndPropagate. That matches the
    // typical "Space activates a button" expectation.
  };

  const isInline = size === "inline";
  // Touch target sizes: even the "inline" mode now uses h-8 (32px)
  // for inputs and h-8 toggle buttons so finger taps on mobile/iPad
  // land cleanly. Form mode uses h-9 (36px) for desktop comfort.
  // Hour input is wider than minute to accommodate the power-user
  // shortcut where the whole time is typed into the hour field
  // ("13:30", "9:30 PM", "0930"). Minute input stays narrow because
  // it only ever holds 2 digits.
  const hourInputCls = isInline
    ? "h-8 w-12 border border-blue-400 rounded px-1 py-0 text-sm bg-white text-center placeholder:text-blue-300/70 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500"
    : "flex h-9 w-16 rounded-md border border-input bg-background px-2 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 text-center";
  const minuteInputCls = isInline
    ? "h-8 w-10 border border-blue-400 rounded px-1 py-0 text-sm bg-white text-center placeholder:text-blue-300/70 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-500"
    : "flex h-9 w-12 rounded-md border border-input bg-background px-2 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 text-center";
  const separatorCls = isInline
    ? "text-sm text-gray-500 select-none px-0.5"
    : "text-sm text-gray-500 select-none";

  const buttonBase =
    "border font-medium select-none disabled:opacity-50 disabled:cursor-not-allowed transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring";
  const buttonSize = isInline
    ? "px-2 h-8 text-xs leading-none min-w-[2rem]"
    : "px-2.5 py-1 text-xs leading-tight h-9";

  const amActive = period === "am";
  const pmActive = period === "pm";

  const resolvedMinuteTestId =
    minuteTestId ?? (inputTestId ? `${inputTestId}-minute` : undefined);
  const resolvedMinuteAriaLabel =
    minuteAriaLabel ?? (ariaLabel ? `${ariaLabel} (minutes)` : undefined);

  return (
    <div
      // Visible gap between the [hour:minute] group and the AM/PM
      // toggle so a thumb-tap on the toggle on a touch device
      // doesn't accidentally land on the minute input.
      className="inline-flex items-center gap-1.5"
      // Lock LTR so "[hour]:[minute] [AM|PM]" reads left-to-right
      // even inside the surrounding RTL Arabic page. Matches task
      // #292's lock on the inline editor.
      dir="ltr"
    >
      <input
        ref={hourRef}
        id={inputId}
        type="text"
        inputMode="numeric"
        autoComplete="off"
        spellCheck={false}
        placeholder={hourPlaceholder}
        value={hour}
        disabled={disabled}
        onChange={handleHourChange}
        onBlur={handleHourBlur}
        onKeyDown={handleInputKeyDown}
        aria-label={ariaLabel}
        className={hourInputCls}
        data-testid={inputTestId}
      />
      <span className={separatorCls}>:</span>
      <input
        ref={minuteRef}
        type="text"
        inputMode="numeric"
        autoComplete="off"
        spellCheck={false}
        placeholder={minutePlaceholder}
        value={minute}
        // Minute is always 2 digits — guard against accidental
        // overrun (e.g. user keeps typing past the field).
        maxLength={2}
        disabled={disabled}
        onChange={handleMinuteChange}
        onBlur={handleMinuteBlur}
        onKeyDown={handleInputKeyDown}
        aria-label={resolvedMinuteAriaLabel}
        className={minuteInputCls}
        data-testid={resolvedMinuteTestId}
      />
      <div
        className="inline-flex"
        role="radiogroup"
        aria-label={groupLabel}
      >
        <button
          type="button"
          role="radio"
          aria-checked={amActive}
          aria-pressed={amActive}
          disabled={disabled}
          onMouseDown={(e) => e.preventDefault()}
          onClick={() => setPeriodAndPropagate("am")}
          onKeyDown={(e) => handleButtonKeyDown(e, "am")}
          className={
            buttonBase +
            " " +
            buttonSize +
            " rounded-s-md " +
            (amActive
              ? "bg-blue-600 text-white border-blue-600 z-10"
              : "bg-white text-blue-700 border-blue-400 hover:bg-blue-50")
          }
          data-testid={amTestId}
        >
          {amLabel}
        </button>
        <button
          type="button"
          role="radio"
          aria-checked={pmActive}
          aria-pressed={pmActive}
          disabled={disabled}
          onMouseDown={(e) => e.preventDefault()}
          onClick={() => setPeriodAndPropagate("pm")}
          onKeyDown={(e) => handleButtonKeyDown(e, "pm")}
          className={
            buttonBase +
            " " +
            buttonSize +
            " rounded-e-md -ms-px " +
            (pmActive
              ? "bg-blue-600 text-white border-blue-600 z-10"
              : "bg-white text-blue-700 border-blue-400 hover:bg-blue-50")
          }
          data-testid={pmTestId}
        >
          {pmLabel}
        </button>
      </div>
    </div>
  );
}

export const TimePicker12h = forwardRef<TimePicker12hHandle, Props>(
  TimePicker12hImpl,
);
TimePicker12h.displayName = "TimePicker12h";
