// ui-datetime.jsx — поля даты и времени со своим пикером.
//
// Нативные <input type="date"|"time"> рисуются средствами ОС: на тёмной теме
// сайта они выпадают светлым системным календарём, шрифтом и радиусами мимо
// всего остального. Здесь и поле, и всплывашка — обычная разметка, поэтому
// подчиняются тем же переменным, что Select и .input.
//
// Клавиатура остаётся первичным способом ввода: маска ДД.ММ.ГГГГ / чч:мм
// работает без открытия пикера. Для даты рождения это единственный быстрый
// путь — календарём до 1991 года никто листать не станет, поэтому у него есть
// режимы «месяцы» и «годы» (клик по заголовку).

const DT_MONTHS = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
  'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'];
const DT_MONTHS_SHORT = ['янв', 'фев', 'мар', 'апр', 'май', 'июн',
  'июл', 'авг', 'сен', 'окт', 'ноя', 'дек'];
const DT_WEEKDAYS = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];

const dtPad2 = (n) => String(n).padStart(2, '0');
const dtDaysInMonth = (y, m0) => new Date(y, m0 + 1, 0).getDate();
// Неделя начинается с понедельника: JS отдаёт 0 для воскресенья.
const dtFirstWeekday = (y, m0) => (new Date(y, m0, 1).getDay() + 6) % 7;

const dtValidYMD = (y, m, d) =>
  y >= 1 && m >= 1 && m <= 12 && d >= 1 && d <= dtDaysInMonth(y, m - 1);

// "ДД.ММ.ГГГГ" → "ГГГГ-ММ-ДД"; несуществующая дата (31.02) → "".
const dtDotToISO = (s) => {
  const m = /^(\d{2})\.(\d{2})\.(\d{4})$/.exec((s || '').trim());
  if (!m) return '';
  return dtValidYMD(+m[3], +m[2], +m[1]) ? `${m[3]}-${m[2]}-${m[1]}` : '';
};

// "ГГГГ-ММ-ДД[что угодно]" → "ДД.ММ.ГГГГ".
const dtISOToDot = (s) => {
  const m = /^(\d{4})-(\d{2})-(\d{2})/.exec((s || '').trim());
  if (!m) return '';
  return dtValidYMD(+m[1], +m[2], +m[3]) ? `${m[3]}.${m[2]}.${m[1]}` : '';
};

// Маска даты: из любых символов оставляем цифры и расставляем точки по мере
// ввода. Бэкспейс работает естественно — точки уходят вместе с цифрами.
const dtMaskDate = (s) => {
  const d = (s || '').replace(/\D/g, '').slice(0, 8);
  const parts = [d.slice(0, 2)];
  if (d.length >= 3) parts.push(d.slice(2, 4));
  if (d.length >= 5) parts.push(d.slice(4, 8));
  return parts.filter(Boolean).join('.');
};

// Маска времени: цифры → чч:мм.
const dtMaskTime = (s) => {
  const d = (s || '').replace(/\D/g, '').slice(0, 4);
  return d.length <= 2 ? d : d.slice(0, 2) + ':' + d.slice(2, 4);
};

// Время в каноническом виде "HH:MM" или "" — терпимо к "04:30:00.000000",
// которое может прийти из базы.
const dtNormTime = (s) => {
  const m = /^(\d{1,2}):(\d{2})/.exec((s || '').trim());
  if (!m) return '';
  const h = +m[1], mi = +m[2];
  if (h > 23 || mi > 59) return '';
  return `${dtPad2(h)}:${dtPad2(mi)}`;
};

// Закрытие по клику мимо и по Escape — общее поведение обеих всплывашек.
function useDtPopover(open, close) {
  const rootRef = React.useRef(null);
  React.useEffect(() => {
    if (!open) return undefined;
    const onDown = (e) => {
      if (rootRef.current && !rootRef.current.contains(e.target)) close();
    };
    const onKey = (e) => { if (e.key === 'Escape') { e.stopPropagation(); close(); } };
    document.addEventListener('mousedown', onDown);
    document.addEventListener('keydown', onKey, true);
    return () => {
      document.removeEventListener('mousedown', onDown);
      document.removeEventListener('keydown', onKey, true);
    };
  }, [open, close]);
  return rootRef;
}

// Всплывашка не должна уезжать за нижний край: если места мало — раскрываем вверх.
function useDtDirection(open, rootRef, height) {
  const [up, setUp] = React.useState(false);
  React.useEffect(() => {
    if (!open || !rootRef.current) return;
    const r = rootRef.current.getBoundingClientRect();
    setUp(r.bottom + height > window.innerHeight && r.top > height);
  }, [open]);
  return up;
}

// ——— DateField ———
// value/onChange — "ГГГГ-ММ-ДД" (format="iso", по умолчанию) либо "ДД.ММ.ГГГГ"
// (format="dot"). Неполный ввод отдаётся наверх как "": незаконченная дата
// не должна выглядеть как введённая.
function DateField({
  value,
  onChange,
  format = 'iso',
  placeholder = 'ДД.ММ.ГГГГ',
  minYear = 1900,
  maxYear = new Date().getFullYear() + 1,
  disabled = false,
  id,
  className = '',
}) {
  const iso = format === 'dot' ? dtDotToISO(value) : (dtISOToDot(value) ? String(value).slice(0, 10) : '');
  const emit = (nextISO) => onChange(!nextISO ? '' : (format === 'dot' ? dtISOToDot(nextISO) : nextISO));

  const [text, setText] = React.useState(dtISOToDot(iso));
  const [open, setOpen] = React.useState(false);
  const [view, setView] = React.useState('days'); // days | months | years
  const today = React.useMemo(() => new Date(), []);
  const [cursor, setCursor] = React.useState(() => {
    const d = iso ? new Date(iso + 'T00:00:00') : today;
    return { y: d.getFullYear(), m: d.getMonth() };
  });

  // Внешнее изменение value перерисовывает поле, но не затирает набранное
  // вручную: сравниваем по смыслу, а не по строке.
  React.useEffect(() => {
    if (iso !== dtDotToISO(text)) setText(iso ? dtISOToDot(iso) : '');
  }, [iso]);

  const close = React.useCallback(() => { setOpen(false); setView('days'); }, []);
  const rootRef = useDtPopover(open, close);
  const up = useDtDirection(open, rootRef, 360);

  React.useEffect(() => {
    if (!open) return;
    const d = iso ? new Date(iso + 'T00:00:00') : today;
    setCursor({ y: d.getFullYear(), m: d.getMonth() });
  }, [open]);

  const onText = (raw) => {
    const masked = dtMaskDate(raw);
    setText(masked);
    emit(dtDotToISO(masked));
  };

  const pickDay = (d) => {
    const next = `${cursor.y}-${dtPad2(cursor.m + 1)}-${dtPad2(d)}`;
    setText(dtISOToDot(next));
    emit(next);
    close();
  };

  const shiftMonth = (delta) => setCursor(c => {
    const m = c.m + delta;
    return { y: c.y + Math.floor(m / 12), m: ((m % 12) + 12) % 12 };
  });

  const yearPageStart = Math.floor((cursor.y - minYear) / 24) * 24 + minYear;

  const cells = React.useMemo(() => {
    const lead = dtFirstWeekday(cursor.y, cursor.m);
    const total = dtDaysInMonth(cursor.y, cursor.m);
    const out = [];
    for (let i = 0; i < lead; i++) out.push(null);
    for (let d = 1; d <= total; d++) out.push(d);
    while (out.length % 7 !== 0) out.push(null);
    return out;
  }, [cursor.y, cursor.m]);

  const selISO = iso;
  const todayISO = `${today.getFullYear()}-${dtPad2(today.getMonth() + 1)}-${dtPad2(today.getDate())}`;

  return (
    <div className={'ui-dt' + (open ? ' open' : '') + (className ? ' ' + className : '')} ref={rootRef}>
      <div className="ui-dt-control">
        <input
          id={id}
          className="input mono ui-dt-input"
          inputMode="numeric"
          autoComplete="off"
          maxLength={10}
          disabled={disabled}
          value={text}
          placeholder={placeholder}
          onChange={e => onText(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter' && open) { e.preventDefault(); close(); } }}
        />
        <button
          type="button"
          className="ui-dt-btn"
          disabled={disabled}
          aria-label="Выбрать дату"
          aria-expanded={open}
          onClick={() => !disabled && (open ? close() : setOpen(true))}
        >
          <IconCalendar size={15} />
        </button>
      </div>

      {open && (
        <div className={'ui-dt-pop ui-dt-cal' + (up ? ' up' : '')}>
          <div className="ui-dt-head">
            <button type="button" className="ui-dt-nav" aria-label="Назад"
              onClick={() => view === 'days' ? shiftMonth(-1) : setCursor(c => ({ ...c, y: c.y - (view === 'months' ? 1 : 24) }))}>
              <IconChevRight size={14} style={{ transform: 'rotate(180deg)' }} />
            </button>
            <button type="button" className="ui-dt-title"
              onClick={() => setView(v => v === 'days' ? 'months' : v === 'months' ? 'years' : 'days')}>
              {view === 'days' && `${DT_MONTHS[cursor.m]} ${cursor.y}`}
              {view === 'months' && cursor.y}
              {view === 'years' && `${yearPageStart} — ${yearPageStart + 23}`}
            </button>
            <button type="button" className="ui-dt-nav" aria-label="Вперёд"
              onClick={() => view === 'days' ? shiftMonth(1) : setCursor(c => ({ ...c, y: c.y + (view === 'months' ? 1 : 24) }))}>
              <IconChevRight size={14} />
            </button>
          </div>

          {view === 'days' && (
            <React.Fragment>
              <div className="ui-dt-week">
                {DT_WEEKDAYS.map(w => <span key={w}>{w}</span>)}
              </div>
              <div className="ui-dt-grid">
                {cells.map((d, i) => {
                  if (!d) return <span key={i} className="ui-dt-day empty" />;
                  const cellISO = `${cursor.y}-${dtPad2(cursor.m + 1)}-${dtPad2(d)}`;
                  return (
                    <button
                      key={i}
                      type="button"
                      className={'ui-dt-day' + (cellISO === selISO ? ' selected' : '') + (cellISO === todayISO ? ' today' : '')}
                      onClick={() => pickDay(d)}
                    >{d}</button>
                  );
                })}
              </div>
            </React.Fragment>
          )}

          {view === 'months' && (
            <div className="ui-dt-grid months">
              {DT_MONTHS_SHORT.map((mo, i) => (
                <button key={mo} type="button"
                  className={'ui-dt-cell' + (i === cursor.m ? ' selected' : '')}
                  onClick={() => { setCursor(c => ({ ...c, m: i })); setView('days'); }}
                >{mo}</button>
              ))}
            </div>
          )}

          {view === 'years' && (
            <div className="ui-dt-grid years">
              {Array.from({ length: 24 }, (_, i) => yearPageStart + i)
                .filter(y => y >= minYear && y <= maxYear)
                .map(y => (
                  <button key={y} type="button"
                    className={'ui-dt-cell' + (y === cursor.y ? ' selected' : '')}
                    onClick={() => { setCursor(c => ({ ...c, y })); setView('months'); }}
                  >{y}</button>
                ))}
            </div>
          )}

          <div className="ui-dt-foot">
            <button type="button" className="ui-dt-link" onClick={() => { setText(''); emit(''); close(); }}>
              Очистить
            </button>
            <button type="button" className="ui-dt-link" onClick={() => {
              setCursor({ y: today.getFullYear(), m: today.getMonth() });
              setView('days');
            }}>
              Сегодня
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

// ——— TimeField ———
// value/onChange — "ЧЧ:ММ" либо "". Часы и минуты выбираются двумя колонками:
// это единственная раскладка, которая на телефоне не превращается в рулетку
// из системного пикера.
function TimeField({
  value,
  onChange,
  placeholder = 'чч:мм',
  disabled = false,
  minuteStep = 1,
  id,
  className = '',
}) {
  const norm = dtNormTime(value);
  const [text, setText] = React.useState(norm);
  const [open, setOpen] = React.useState(false);
  const hoursRef = React.useRef(null);
  const minsRef = React.useRef(null);

  React.useEffect(() => {
    if (norm !== dtNormTime(text)) setText(norm);
  }, [norm]);

  const close = React.useCallback(() => setOpen(false), []);
  const rootRef = useDtPopover(open, close);
  const up = useDtDirection(open, rootRef, 260);

  // При открытии подкручиваем колонки к выбранному значению.
  React.useEffect(() => {
    if (!open) return;
    [hoursRef, minsRef].forEach(ref => {
      const el = ref.current && ref.current.querySelector('.selected');
      if (el && el.scrollIntoView) el.scrollIntoView({ block: 'center' });
    });
  }, [open]);

  const hh = norm ? norm.slice(0, 2) : '';
  const mm = norm ? norm.slice(3, 5) : '';

  const onText = (raw) => {
    const masked = dtMaskTime(raw);
    setText(masked);
    onChange(dtNormTime(masked));
  };

  const set = (nextH, nextM) => {
    const v = `${nextH}:${nextM}`;
    setText(v);
    onChange(v);
  };

  const minutes = React.useMemo(
    () => Array.from({ length: Math.ceil(60 / minuteStep) }, (_, i) => dtPad2(i * minuteStep)),
    [minuteStep]
  );

  return (
    <div className={'ui-dt' + (open ? ' open' : '') + (className ? ' ' + className : '')} ref={rootRef}>
      <div className="ui-dt-control">
        <input
          id={id}
          className="input mono ui-dt-input"
          inputMode="numeric"
          autoComplete="off"
          maxLength={5}
          disabled={disabled}
          value={text}
          placeholder={placeholder}
          onChange={e => onText(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter' && open) { e.preventDefault(); close(); } }}
        />
        <button
          type="button"
          className="ui-dt-btn"
          disabled={disabled}
          aria-label="Выбрать время"
          aria-expanded={open}
          onClick={() => !disabled && (open ? close() : setOpen(true))}
        >
          <IconClock size={15} />
        </button>
      </div>

      {open && (
        <div className={'ui-dt-pop ui-dt-time' + (up ? ' up' : '')}>
          <div className="ui-dt-cols">
            <div className="ui-dt-col" ref={hoursRef}>
              <div className="ui-dt-col-cap">час</div>
              <div className="ui-dt-col-list">
                {Array.from({ length: 24 }, (_, i) => dtPad2(i)).map(h => (
                  <button key={h} type="button"
                    className={'ui-dt-tick' + (h === hh ? ' selected' : '')}
                    onClick={() => set(h, mm || '00')}
                  >{h}</button>
                ))}
              </div>
            </div>
            <div className="ui-dt-col" ref={minsRef}>
              <div className="ui-dt-col-cap">мин</div>
              <div className="ui-dt-col-list">
                {minutes.map(m => (
                  <button key={m} type="button"
                    className={'ui-dt-tick' + (m === mm ? ' selected' : '')}
                    onClick={() => { set(hh || '12', m); close(); }}
                  >{m}</button>
                ))}
              </div>
            </div>
          </div>
          <div className="ui-dt-foot">
            <button type="button" className="ui-dt-link" onClick={() => { setText(''); onChange(''); close(); }}>
              Не знаю
            </button>
            <button type="button" className="ui-dt-link" onClick={close}>Готово</button>
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  DateField, TimeField,
  dtMaskDate, dtMaskTime, dtDotToISO, dtISOToDot, dtNormTime,
});
