import { type ReactElement } from "react";
import * as React from "react";
import { ChevronDown, Clock } from "lucide-react";
import { cn } from "@/lib/utils";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";

/**
 * TimePicker — WealthX Design System
 *
 * A compact **segmented** time input (openstatus-style): `HH : MM AM/PM`. Each
 * segment is directly editable — type digits, use ↑/↓ to increment/decrement
 * (values loop), and ←/→ to move between segments. Two-digit entry auto-advances
 * to the next segment. Keyboard-first, no long dropdown list.
 *
 * Controlled: `value` / `onChange` use a 24-hour `"HH:MM"` string (e.g.
 * `"09:00"`, `"17:30"`), so it drops into forms alongside `<input type="time">`
 * values without conversion.
 *
 * `minuteStep` constrains the minute to a grid — `30` allows only `:00`/`:30`
 * (arrow keys jump by it; typed minutes snap to the nearest). Default `1`.
 *
 * `selectable` adds a chevron that opens a scrollable list of times (on the
 * `minuteStep` grid), so the value can be picked with the mouse like a date
 * picker — the segments stay keyboard-editable. Default `false`.
 */

type Period = "AM" | "PM";

const pad = (n: number) => String(n).padStart(2, "0");

function label12(h: number, m: number): string {
  const h12 = h % 12 === 0 ? 12 : h % 12;
  return `${h12}:${pad(m)} ${h < 12 ? "AM" : "PM"}`;
}

// Falls back to an hourly grid when minuteStep is too fine (<5) to list, so a
// default any-minute picker never builds 1440 rows.
function buildTimeOptions(minuteStep: number): { value: string; label: string }[] {
  const step = minuteStep >= 5 ? minuteStep : 60;
  const options: { value: string; label: string }[] = [];
  for (let mins = 0; mins < 24 * 60; mins += step) {
    const h = Math.floor(mins / 60);
    const m = mins % 60;
    options.push({ value: `${pad(h)}:${pad(m)}`, label: label12(h, m) });
  }
  return options;
}

/** Parse a 24h "HH:MM" string into 12-hour parts. */
function to12(value?: string): {
  hour: number;
  minute: number;
  period: Period;
} {
  if (!value) return { hour: 12, minute: 0, period: "AM" };
  const [h, m] = value.split(":");
  const h24 = Math.min(23, Math.max(0, Number(h) || 0));
  const minute = Math.min(59, Math.max(0, Number(m) || 0));
  const period: Period = h24 < 12 ? "AM" : "PM";
  const hour = h24 % 12 === 0 ? 12 : h24 % 12;
  return { hour, minute, period };
}

/** Build a 24h "HH:MM" string from 12-hour parts. */
function to24({
  hour,
  minute,
  period,
}: {
  hour: number;
  minute: number;
  period: Period;
}): string {
  let h = hour % 12;
  if (period === "PM") h += 12;
  return `${pad(h)}:${pad(minute)}`;
}

interface TimeSegmentProps {
  value: number;
  min: number;
  max: number;
  /** Increment size for arrow keys, and the grid typed values snap to. Default 1. */
  step?: number;
  ariaLabel: string;
  disabled?: boolean;
  onChange: (n: number) => void;
  onLeftFocus?: () => void;
  onRightFocus?: () => void;
}

/** One editable numeric segment (hour or minute) with type + arrow-key entry. */
const TimeSegment = React.forwardRef<HTMLInputElement, TimeSegmentProps>(
  function TimeSegment(
    {
      value,
      min,
      max,
      step = 1,
      ariaLabel,
      disabled,
      onChange,
      onLeftFocus,
      onRightFocus,
    },
    ref
  ): ReactElement {
    const [text, setText] = React.useState(pad(value));
    // true once a first digit is typed and a second digit could still fit.
    const awaitingSecond = React.useRef(false);

    // Nearest in-range multiple of `step` (identity when step <= 1).
    const snap = React.useCallback(
      (n: number): number => {
        const clamped = Math.min(max, Math.max(min, n));
        if (step <= 1) return clamped;
        let s = Math.round(clamped / step) * step;
        if (s > max) s -= step;
        if (s < min) s = min;
        return s;
      },
      [min, max, step]
    );
    const maxValid = snap(max);

    React.useEffect(() => {
      if (!awaitingSecond.current) setText(pad(value));
    }, [value]);

    const commit = (n: number, advance: boolean) => {
      const snapped = snap(n);
      awaitingSecond.current = false;
      setText(pad(snapped));
      onChange(snapped);
      if (advance) onRightFocus?.();
    };

    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
      if (e.key === "Tab") return;
      e.preventDefault();

      if (e.key === "ArrowRight") return onRightFocus?.();
      if (e.key === "ArrowLeft") return onLeftFocus?.();

      if (e.key === "ArrowUp" || e.key === "ArrowDown") {
        let n = value + (e.key === "ArrowUp" ? step : -step);
        if (n > maxValid) n = min; // loop
        if (n < min) n = maxValid;
        awaitingSecond.current = false;
        setText(pad(n));
        onChange(n);
        return;
      }

      if (/^[0-9]$/.test(e.key)) {
        const d = Number(e.key);
        if (!awaitingSecond.current) {
          if (d * 10 <= max) {
            // A second digit could still land within range — show it and wait.
            awaitingSecond.current = true;
            setText(String(d));
            if (d >= min) onChange(snap(d));
          } else {
            commit(d, true);
          }
        } else {
          commit(Number(`${text}${d}`), true);
        }
        return;
      }

      if (e.key === "Backspace") {
        awaitingSecond.current = false;
        setText(pad(min));
        onChange(min);
      }
    };

    return (
      <input
        ref={ref}
        type="text"
        inputMode="numeric"
        value={text}
        onChange={() => {}}
        onKeyDown={handleKeyDown}
        onFocus={(e) => e.currentTarget.select()}
        onBlur={() => {
          // Re-sync to the committed value if a lone digit was left mid-entry.
          awaitingSecond.current = false;
          setText(pad(value));
        }}
        aria-label={ariaLabel}
        disabled={disabled}
        className="w-[2ch] bg-transparent text-center text-body-medium tabular-nums caret-transparent outline-none focus:bg-primary/10 disabled:cursor-not-allowed"
      />
    );
  }
);

interface PeriodSegmentProps {
  value: Period;
  disabled?: boolean;
  onChange: (p: Period) => void;
  onLeftFocus?: () => void;
}

/** AM/PM segment — toggle with ↑/↓ or type "a" / "p". */
const PeriodSegment = React.forwardRef<HTMLInputElement, PeriodSegmentProps>(
  function PeriodSegment(
    { value, disabled, onChange, onLeftFocus },
    ref
  ): ReactElement {
    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
      if (e.key === "Tab") return;
      e.preventDefault();
      if (e.key === "ArrowLeft") return onLeftFocus?.();
      if (e.key === "ArrowUp" || e.key === "ArrowDown") {
        return onChange(value === "AM" ? "PM" : "AM");
      }
      if (e.key.toLowerCase() === "a") onChange("AM");
      if (e.key.toLowerCase() === "p") onChange("PM");
    };

    return (
      <input
        ref={ref}
        type="text"
        value={value}
        readOnly
        onKeyDown={handleKeyDown}
        onFocus={(e) => e.currentTarget.select()}
        aria-label="AM or PM"
        disabled={disabled}
        className="w-[3ch] bg-transparent text-center text-body-medium caret-transparent outline-none focus:bg-primary/10 disabled:cursor-not-allowed"
      />
    );
  }
);

export interface TimePickerProps {
  /** 24-hour time as "HH:MM" (e.g. "09:00", "17:30"). */
  value?: string;
  onChange?: (value: string) => void;
  disabled?: boolean;
  /** Render the error (destructive) border/ring. */
  invalid?: boolean;
  /**
   * Minute granularity. Arrow keys step by this, and any typed minute snaps to
   * the nearest multiple — e.g. `30` restricts the minute to `:00`/`:30`.
   * Default `1` (any minute).
   */
  minuteStep?: number;
  /** Add a chevron that opens a scrollable, clickable list of times. Default false. */
  selectable?: boolean;
  id?: string;
  className?: string;
}

export function TimePicker({
  value,
  onChange,
  disabled,
  invalid,
  minuteStep = 1,
  selectable = false,
  id,
  className,
}: TimePickerProps): ReactElement {
  const { hour, minute, period } = to12(value);
  const hourRef = React.useRef<HTMLInputElement>(null);
  const minuteRef = React.useRef<HTMLInputElement>(null);
  const periodRef = React.useRef<HTMLInputElement>(null);
  const [open, setOpen] = React.useState(false);
  const selectedOptionRef = React.useRef<HTMLButtonElement>(null);

  const emit = (
    next: Partial<{ hour: number; minute: number; period: Period }>
  ) =>
    onChange?.(
      to24({
        hour: next.hour ?? hour,
        minute: next.minute ?? minute,
        period: next.period ?? period,
      })
    );

  const currentValue = to24({ hour, minute, period });
  const options = React.useMemo(
    () => (selectable ? buildTimeOptions(minuteStep) : []),
    [selectable, minuteStep]
  );

  React.useEffect(() => {
    if (open) selectedOptionRef.current?.scrollIntoView({ block: "center" });
  }, [open]);

  const boxClassName = cn(
    "flex h-9 w-full items-center border border-input bg-transparent px-3 shadow-xs transition-[color,box-shadow] outline-none",
    "focus-within:border-primary focus-within:ring-[3px] focus-within:ring-primary/20",
    "aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20",
    disabled && "pointer-events-none opacity-50",
    className
  );

  const segments = (
    <>
      <Clock className="mr-2 size-4 shrink-0 text-muted-foreground" />
      <TimeSegment
        ref={hourRef}
        value={hour}
        min={1}
        max={12}
        ariaLabel="Hour"
        disabled={disabled}
        onChange={(h) => emit({ hour: h })}
        onRightFocus={() => minuteRef.current?.focus()}
      />
      <span className="text-muted-foreground">:</span>
      <TimeSegment
        ref={minuteRef}
        value={minute}
        min={0}
        max={59}
        step={minuteStep}
        ariaLabel="Minute"
        disabled={disabled}
        onChange={(m) => emit({ minute: m })}
        onLeftFocus={() => hourRef.current?.focus()}
        onRightFocus={() => periodRef.current?.focus()}
      />
      <PeriodSegment
        ref={periodRef}
        value={period}
        disabled={disabled}
        onChange={(p) => emit({ period: p })}
        onLeftFocus={() => minuteRef.current?.focus()}
      />
    </>
  );

  if (!selectable) {
    return (
      <div
        id={id}
        role="group"
        aria-label="Time"
        aria-invalid={invalid || undefined}
        data-slot="time-picker"
        onClick={() => hourRef.current?.focus()}
        className={boxClassName}
      >
        {segments}
      </div>
    );
  }

  return (
    <Popover onOpenChange={disabled ? undefined : setOpen} open={open}>
      <div
        id={id}
        role="group"
        aria-label="Time"
        aria-invalid={invalid || undefined}
        data-slot="time-picker"
        className={boxClassName}
      >
        {segments}
        <div className="flex-1" />
        <PopoverTrigger
          render={
            <button
              aria-label="Choose a time"
              className="-mr-1 flex size-6 shrink-0 items-center justify-center text-muted-foreground outline-none hover:text-foreground focus-visible:text-foreground disabled:cursor-not-allowed"
              disabled={disabled}
              type="button"
            />
          }
        >
          <ChevronDown className="size-4" />
        </PopoverTrigger>
      </div>
      <PopoverContent
        align="end"
        className="max-h-60 w-40 overflow-y-auto p-1"
        sideOffset={6}
      >
        {options.map((option) => {
          const isSelected = option.value === currentValue;
          return (
            <button
              key={option.value}
              ref={isSelected ? selectedOptionRef : undefined}
              className={cn(
                "flex w-full items-center px-3 py-1.5 text-left text-body-medium tabular-nums outline-none hover:bg-primary/10 focus-visible:bg-primary/10",
                isSelected && "bg-primary/10 font-medium text-primary"
              )}
              onClick={() => {
                onChange?.(option.value);
                setOpen(false);
              }}
              type="button"
            >
              {option.label}
            </button>
          );
        })}
      </PopoverContent>
    </Popover>
  );
}

export default TimePicker;
