/**
 * TimePicker — 时间选择器
 * @prop {'time'|'timerange'} [type='time'] — 选择类型
 * @prop {string|Array<string>} value — 当前值（受控）
 * @prop {string|Array<string>} [defaultValue=undefined] — 非受控初始值
 * @prop {string} [placeholder=''] — 占位文案
 * @prop {boolean} [disabled=false] — 是否禁用
 * @prop {boolean} [defaultOpen=false] — 初始是否展开
 * @prop {Function} [onChange=null] — 变更回调
 * @prop {string} [className=''] — 额外类名
 */

import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';

const PANEL_GAP = 4;
const PANEL_Z = 10000;
const TIME_COLUMN_WIDTH = 84;
const PANEL_WIDTH_TIME = TIME_COLUMN_WIDTH * 2;
const PANEL_WIDTH_TIME_RANGE = TIME_COLUMN_WIDTH * 4;

const TYPE_CONFIG = {
  time: {
    widthClass: 'w-full min-w-0 max-w-full',
    placeholder: '请选择时间',
    isRange: false,
    panelWidth: PANEL_WIDTH_TIME,
    panelHeight: 252,
  },
  timerange: {
    widthClass: 'w-full min-w-0 max-w-full',
    placeholderStart: '开始时间',
    placeholderEnd: '结束时间',
    isRange: true,
    panelWidth: PANEL_WIDTH_TIME_RANGE,
    panelHeight: 304,
  },
};

const FRAME_BASE = [
  'relative inline-flex h-[36px] items-center',
  'border border-solid border-border rounded-md',
  'bg-surface pl-3 pr-2 gap-2',
  '[font-family:inherit] outline-none cursor-pointer',
  'transition-colors tfds-motion-ui-fast',
  'hover:border-blueGrey-400',
  'focus-within:border-primary focus-within:hover:border-primary',
].join(' ');

const DISABLED_CLASS = 'bg-disabled border-border cursor-not-allowed opacity-60 pointer-events-none';
const TEXT_BASE = 'text-sm leading-[20px]';
const VALUE_TEXT = [TEXT_BASE, 'text-foreground truncate'].join(' ');
const PLACEHOLDER_TEXT = [TEXT_BASE, 'text-foreground-muted truncate'].join(' ');
const RANGE_SEP = [TEXT_BASE, 'text-foreground-muted shrink-0'].join(' ');
const CONTENT_SLOT = 'min-w-0 flex-1 text-left';
const ICON_SLOT = 'inline-flex size-[16px] shrink-0 items-center justify-center text-foreground-disabled [&>svg]:size-[16px]';

const PANEL_BASE = [
  'overflow-hidden rounded-md border border-solid border-border',
  'bg-surface shadow-lg',
].join(' ');
const PANEL_HEADER = 'flex h-[52px] items-center justify-center border-b border-solid border-b-border px-3 text-sm [font-weight:var(--font-semibold)] text-foreground';
const PANEL_BODY = 'relative h-[252px] overflow-hidden';
const WHEEL = 'relative flex h-full w-full justify-center overflow-hidden';
const SELECTOR_AREA = 'pointer-events-none absolute left-0 right-0 top-1/2 z-10 h-[36px] -translate-y-1/2 bg-brand-50';
const COLUMN = 'relative z-20 h-full w-[84px] shrink-0 overflow-y-auto py-[108px] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden';
const COLUMN_DIVIDER = 'border-r border-solid border-r-border';
const OPTION = [
  'flex h-[36px] w-full items-center justify-center',
  'border-none bg-transparent px-0',
  'text-sm leading-[36px] tracking-normal text-foreground-secondary',
  'hover:bg-transparent hover:text-foreground',
  'transition-colors tfds-motion-ui-instant',
].join(' ');
const OPTION_ACTIVE = 'text-foreground hover:bg-transparent hover:text-foreground';
const OPTION_UNIT = 'text-sm leading-[36px] font-normal text-foreground';
const ITEM_HEIGHT = 36;

function pad2(n) {
  return String(n).padStart(2, '0');
}

const HOURS = Array.from({ length: 24 }, (_, i) => pad2(i));
const MINUTES = Array.from({ length: 60 }, (_, i) => pad2(i));

function ClockIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" className="size-[16px]" aria-hidden="true">
      <circle cx="8" cy="8" r="5.5" stroke="currentColor" strokeWidth="1.2" />
      <path d="M8 5.4V8L9.8 9" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function parseTime(raw, fallback = '') {
  if (typeof raw !== 'string') return fallback;
  const match = raw.trim().match(/^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/);
  if (!match) return fallback;
  const h = Math.max(0, Math.min(23, Number(match[1])));
  const m = Math.max(0, Math.min(59, Number(match[2])));
  return `${pad2(h)}:${pad2(m)}`;
}

function splitTime(raw, fallback = '09:00') {
  const [hour, minute] = parseTime(raw, fallback).split(':');
  return [hour, minute];
}

function normalizeValue(raw, config) {
  if (!config.isRange) return parseTime(raw, '');
  if (!Array.isArray(raw)) return ['', ''];
  return [parseTime(raw[0], ''), parseTime(raw[1], '')];
}

function measurePanel(triggerEl, panelWidth, panelHeight) {
  const rect = triggerEl.getBoundingClientRect();
  const spaceBelow = window.innerHeight - rect.bottom - PANEL_GAP;
  const spaceAbove = rect.top - PANEL_GAP;
  const placeBelow = spaceBelow >= panelHeight || spaceBelow >= spaceAbove;

  const top = placeBelow
    ? rect.bottom + PANEL_GAP
    : Math.max(8, rect.top - panelHeight - PANEL_GAP);

  let left = rect.left;
  if (left + panelWidth > window.innerWidth - 8) {
    left = Math.max(8, window.innerWidth - panelWidth - 8);
  }

  return { top, left, width: panelWidth };
}

function TimeColumn({ part, values, active, unit, showDivider, onPick }) {
  const columnRef = useRef(null);
  const scrollTimerRef = useRef(null);
  const isScrollingRef = useRef(false);
  const lastPickedRef = useRef(active);

  useEffect(() => {
    lastPickedRef.current = active;
    const node = columnRef.current;
    const activeIndex = values.indexOf(active);
    if (!node || activeIndex < 0) return;
    if (isScrollingRef.current) return;
    node.scrollTop = activeIndex * ITEM_HEIGHT;
  }, [active, values]);

  useEffect(() => () => {
    if (scrollTimerRef.current) window.clearTimeout(scrollTimerRef.current);
  }, []);

  const handleScroll = () => {
    isScrollingRef.current = true;
    const node = columnRef.current;
    if (node) {
      const nextIndex = Math.max(0, Math.min(values.length - 1, Math.round(node.scrollTop / ITEM_HEIGHT)));
      const nextValue = values[nextIndex];
      if (nextValue && nextValue !== lastPickedRef.current) {
        lastPickedRef.current = nextValue;
        onPick(part, nextValue);
      }
    }

    if (scrollTimerRef.current) window.clearTimeout(scrollTimerRef.current);
    scrollTimerRef.current = window.setTimeout(() => {
      const currentNode = columnRef.current;
      if (!currentNode) return;
      const nextIndex = Math.max(0, Math.min(values.length - 1, Math.round(currentNode.scrollTop / ITEM_HEIGHT)));
      currentNode.scrollTo({ top: nextIndex * ITEM_HEIGHT, behavior: 'smooth' });
      window.setTimeout(() => {
        isScrollingRef.current = false;
      }, 120);
    }, 120);
  };

  const pickItem = (item) => {
    const node = columnRef.current;
    const nextIndex = values.indexOf(item);
    isScrollingRef.current = false;
    lastPickedRef.current = item;
    if (node && nextIndex >= 0) node.scrollTo({ top: nextIndex * ITEM_HEIGHT, behavior: 'smooth' });
    onPick(part, item);
  };

  return (
    <div ref={columnRef} className={[COLUMN, showDivider ? COLUMN_DIVIDER : ''].filter(Boolean).join(' ')} onScroll={handleScroll}>
      {values.map((item) => {
        const selected = item === active;
        return (
          <button
            key={item}
            type="button"
            className={[OPTION, selected ? OPTION_ACTIVE : ''].filter(Boolean).join(' ')}
            onClick={() => pickItem(item)}
          >
            {item}{selected ? <span className={OPTION_UNIT}>{unit}</span> : null}
          </button>
        );
      })}
    </div>
  );
}

function TimeColumns({ value, fallback, onPick }) {
  const [hour, minute] = splitTime(value, fallback);
  const columns = [
    { key: 'hour', values: HOURS, active: hour, unit: '时' },
    { key: 'minute', values: MINUTES, active: minute, unit: '分' },
  ];

  const pick = (part, next) => {
    const current = { hour, minute };
    current[part] = next;
    onPick(`${current.hour}:${current.minute}`);
  };

  return columns.map((column, index) => (
    <TimeColumn
      key={column.key}
      part={column.key}
      values={column.values}
      active={column.active}
      unit={column.unit}
      showDivider={index < columns.length - 1}
      onPick={pick}
    />
  ));
}

function TimeWheel({ value, fallback, onPick }) {
  return (
    <div className={WHEEL}>
      <div className={SELECTOR_AREA} />
      <TimeColumns value={value} fallback={fallback} onPick={onPick} />
    </div>
  );
}

function TimeRangeWheel({ startValue, endValue, fallbackStart, fallbackEnd, onPick }) {
  const [startHour, startMinute] = splitTime(startValue, fallbackStart);
  const [endHour, endMinute] = splitTime(endValue, fallbackEnd);
  const columns = [
    { key: 'startHour', values: HOURS, active: startHour, unit: '时' },
    { key: 'startMinute', values: MINUTES, active: startMinute, unit: '分' },
    { key: 'endHour', values: HOURS, active: endHour, unit: '时' },
    { key: 'endMinute', values: MINUTES, active: endMinute, unit: '分' },
  ];

  const pick = (part, next) => {
    const current = { startHour, startMinute, endHour, endMinute };
    current[part] = next;
    onPick([
      `${current.startHour}:${current.startMinute}`,
      `${current.endHour}:${current.endMinute}`,
    ]);
  };

  return (
    <div className={WHEEL}>
      <div className={SELECTOR_AREA} />
      {columns.map((column, index) => (
        <TimeColumn
          key={column.key}
          part={column.key}
          values={column.values}
          active={column.active}
          unit={column.unit}
          showDivider={index < columns.length - 1}
          onPick={pick}
        />
      ))}
    </div>
  );
}

export default function TimePicker({
  type = 'time',
  value,
  defaultValue,
  placeholder,
  disabled = false,
  defaultOpen = false,
  onChange,
  className = '',
  ...rest
}) {
  const config = TYPE_CONFIG[type] || TYPE_CONFIG.time;
  const isControlled = value !== undefined;
  const [innerValue, setInnerValue] = useState(defaultValue);
  const currentRawValue = isControlled ? value : innerValue;
  const normalizedValue = useMemo(() => normalizeValue(currentRawValue, config), [currentRawValue, config]);
  const [open, setOpen] = useState(Boolean(defaultOpen) && !disabled);
  const [panelPos, setPanelPos] = useState({ top: 0, left: 0, width: PANEL_WIDTH_TIME });
  const triggerRef = useRef(null);
  const panelRef = useRef(null);

  useEffect(() => {
    setOpen(Boolean(defaultOpen) && !disabled);
  }, [defaultOpen, disabled, type]);

  const updatePanelPos = useCallback(() => {
    if (!triggerRef.current) return;
    setPanelPos(measurePanel(triggerRef.current, config.panelWidth, config.panelHeight));
  }, [config.panelHeight, config.panelWidth]);

  useLayoutEffect(() => {
    if (!open) return;
    updatePanelPos();
  }, [open, updatePanelPos]);

  useEffect(() => {
    if (!open) return;
    let frameId = 0;
    const startedAt = performance.now();
    const syncDuringPreviewMotion = () => {
      updatePanelPos();
      if (performance.now() - startedAt < 260) {
        frameId = window.requestAnimationFrame(syncDuringPreviewMotion);
      }
    };
    frameId = window.requestAnimationFrame(syncDuringPreviewMotion);
    return () => window.cancelAnimationFrame(frameId);
  }, [open, updatePanelPos]);

  useEffect(() => {
    if (!open) return;
    const onMove = () => updatePanelPos();
    window.addEventListener('resize', onMove);
    window.addEventListener('scroll', onMove, true);
    return () => {
      window.removeEventListener('resize', onMove);
      window.removeEventListener('scroll', onMove, true);
    };
  }, [open, updatePanelPos]);

  useEffect(() => {
    if (!open) return;
    const onDown = (e) => {
      const t = e.target;
      if (triggerRef.current?.contains(t) || panelRef.current?.contains(t)) return;
      setOpen(false);
    };
    const onKey = (e) => {
      if (e.key === 'Escape') setOpen(false);
    };
    document.addEventListener('mousedown', onDown);
    document.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('mousedown', onDown);
      document.removeEventListener('keydown', onKey);
    };
  }, [open]);

  const commitValue = useCallback((nextVal) => {
    if (!isControlled) setInnerValue(nextVal);
    onChange?.(nextVal);
  }, [isControlled, onChange]);

  const triggerClass = [
    FRAME_BASE,
    config.widthClass,
    disabled ? DISABLED_CLASS : '',
    className,
  ].filter(Boolean).join(' ');

  const displaySingle = !config.isRange ? normalizedValue : '';
  const displayStart = config.isRange ? normalizedValue[0] : '';
  const displayEnd = config.isRange ? normalizedValue[1] : '';

  const panel = open && typeof document !== 'undefined' ? createPortal(
    <div
      ref={panelRef}
      className={PANEL_BASE}
      style={{
        position: 'fixed',
        top: panelPos.top,
        left: panelPos.left,
        width: `${panelPos.width}px`,
        height: `${config.panelHeight}px`,
        zIndex: PANEL_Z,
      }}
    >
      {!config.isRange ? (
        <div className={PANEL_BODY}>
          <TimeWheel value={displaySingle} fallback="12:15" onPick={commitValue} />
        </div>
      ) : (
        <>
          <div className="grid grid-cols-2">
            <div className={PANEL_HEADER}>开始时间</div>
            <div className={PANEL_HEADER}>结束时间</div>
          </div>
          <div className={PANEL_BODY}>
            <TimeRangeWheel
              startValue={displayStart}
              endValue={displayEnd}
              fallbackStart="09:30"
              fallbackEnd="18:00"
              onPick={commitValue}
            />
          </div>
        </>
      )}
    </div>,
    document.body,
  ) : null;

  return (
    <>
      <button
        ref={triggerRef}
        type="button"
        className={[`tfds-time-picker`, triggerClass].filter(Boolean).join(' ')}
        disabled={disabled}
        onClick={() => {
          if (!disabled) setOpen((v) => !v);
        }}
        aria-haspopup="dialog"
        aria-expanded={open}
        {...rest}
        data-tfds-component="TimePicker"
      >
        {!config.isRange ? (
          <span className={CONTENT_SLOT}>
            {displaySingle ? (
              <span className={VALUE_TEXT}>{displaySingle}</span>
            ) : (
              <span className={PLACEHOLDER_TEXT}>{placeholder || config.placeholder}</span>
            )}
          </span>
        ) : (
          <span className="flex min-w-0 flex-1 items-center gap-2">
            <span className={[CONTENT_SLOT, displayStart ? VALUE_TEXT : PLACEHOLDER_TEXT].join(' ')}>
              {displayStart || config.placeholderStart}
            </span>
            <span className={RANGE_SEP}>～</span>
            <span className={[CONTENT_SLOT, displayEnd ? VALUE_TEXT : PLACEHOLDER_TEXT].join(' ')}>
              {displayEnd || config.placeholderEnd}
            </span>
          </span>
        )}
        <span className={ICON_SLOT}><ClockIcon /></span>
      </button>
      {panel}
    </>
  );
}
