import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { DatePicker, Input, InputNumber, Select, Spin, Tooltip } from 'antd';
import dayjs, { type Dayjs } from 'dayjs';
import equal from 'fast-deep-equal';
import { usePermission } from '../../hooks/usePermission';
import { useTimeout } from 'usehooks-ts';
import { useEditableCellStore } from '../../store/editableCell/editableCellStore';
import { message } from '../../ui/app-message';
import './style.css';
import { getErrorMessage } from '../../utils/error';

export type EditableCellEditor =
  | 'text'
  | 'textarea'
  | 'number'
  | 'money'
  | 'date'
  | 'date-range'
  | 'select'
  | 'multi-select'
  | 'tags';

export interface EditableCellOption {
  label: React.ReactNode;
  value: string | number | boolean;
}

export interface EditableCellProps<Value = unknown> {
  value: Value;
  children?: React.ReactNode;
  editor?: EditableCellEditor;
  options?: EditableCellOption[];
  permission?: string;
  disabled?: boolean;
  required?: boolean;
  min?: number;
  max?: number;
  placeholder?: string;
  formatValue?: (value: Value) => unknown;
  parseValue?: (value: unknown) => Value;
  onSave: (value: Value) => Promise<void>;
}

export function normalizeEditableValue(value: unknown, editor: EditableCellEditor) {
  if (editor === 'date') return value ? dayjs(value as string) : null;
  if (editor === 'date-range')
    return Array.isArray(value) ? value.map((item) => dayjs(item as string)) : null;
  if (editor === 'number' || editor === 'money') {
    return value === null || value === undefined || value === '' ? null : Number(value);
  }
  if (editor === 'multi-select' || editor === 'tags') return Array.isArray(value) ? value : [];
  return value ?? '';
}

export function serializeEditableValue(value: unknown, editor: EditableCellEditor) {
  if (editor === 'date') return value ? (value as Dayjs).format('YYYY-MM-DD') : undefined;
  if (editor === 'date-range')
    return Array.isArray(value)
      ? value.map((item) => (item as Dayjs).format('YYYY-MM-DD'))
      : undefined;
  if (editor === 'number' || editor === 'money') {
    return value === null || value === undefined || value === '' ? undefined : Number(value);
  }
  if (typeof value === 'string') return value.trim();
  return value;
}

export function editableValuesEqual(left: unknown, right: unknown) {
  return equal(left ?? null, right ?? null);
}

function isEditorOverlay(target: EventTarget | null) {
  return (
    target instanceof Element &&
    !!target.closest('.ant-select-dropdown, .ant-picker-dropdown, .ant-tooltip, .ant-message')
  );
}

const EditableCell = <Value,>({
  value,
  children,
  editor = 'text',
  options,
  permission,
  disabled,
  required,
  min,
  max,
  placeholder,
  formatValue,
  parseValue,
  onSave,
}: EditableCellProps<Value>) => {
  const { hasPermission } = usePermission();
  const idRef = useRef(crypto.randomUUID());
  const rootRef = useRef<HTMLDivElement>(null);
  const touchStartRef = useRef<{ pointerId: number; x: number; y: number } | null>(null);
  const lastTouchTapRef = useRef<{ time: number; x: number; y: number } | null>(null);
  const [editing, setEditing] = useState(false);
  const [saving, setSaving] = useState(false);
  const [draft, setDraft] = useState<unknown>(() =>
    normalizeEditableValue(formatValue ? formatValue(value) : value, editor),
  );
  // 保存成功后短暂显示「撤销」入口：记录保存前的序列化旧值
  const [undoMeta, setUndoMeta] = useState<{ serializedPrevious: unknown } | null>(null);
  // 撤销入口 6 秒后自动消失；useTimeout 在 undoMeta 置空/组件卸载时自动清理
  useTimeout(() => setUndoMeta(null), undoMeta ? 6_000 : null);
  const enabled = !disabled && (!permission || hasPermission(permission));

  const original = useMemo(
    () =>
      serializeEditableValue(
        normalizeEditableValue(formatValue ? formatValue(value) : value, editor),
        editor,
      ),
    [editor, formatValue, value],
  );

  const cancel = useCallback(() => {
    setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
    useEditableCellStore.getState().clearIfActive(idRef.current);
    setEditing(false);
  }, [editor, formatValue, value]);

  const saveValue = useCallback(
    async (nextDraft: unknown) => {
      if (saving) return false;
      const serialized = serializeEditableValue(nextDraft, editor);
      if (required && (serialized === '' || serialized === undefined || serialized === null)) {
        message.error('该字段不能为空');
        return false;
      }
      if (editableValuesEqual(serialized, original)) {
        useEditableCellStore.getState().clearIfActive(idRef.current);
        setEditing(false);
        return true;
      }
      setSaving(true);
      const previousValue = original;
      try {
        await onSave(parseValue ? parseValue(serialized) : (serialized as Value));
        useEditableCellStore.getState().clearIfActive(idRef.current);
        setEditing(false);
        // 提供 6 秒内的撤销入口（把旧值再保存一次）；useTimeout 负责到时自动清除
        setUndoMeta({ serializedPrevious: previousValue });
        return true;
      } catch (error) {
        message.error(getErrorMessage(error, '保存失败'));
        return false;
      } finally {
        setSaving(false);
      }
    },
    [editor, onSave, original, parseValue, required, saving],
  );

  const save = useCallback(() => saveValue(draft), [draft, saveValue]);

  useEffect(() => {
    const cellId = idRef.current;
    if (editing) useEditableCellStore.getState().updateActiveSave(cellId, save);
    return () => useEditableCellStore.getState().clearIfActive(cellId);
  }, [editing, save]);

  useEffect(() => {
    if (!editing) return;
    const onPointerDown = (event: PointerEvent) => {
      if (useEditableCellStore.getState().replayingOutsideAction) return;
      if (rootRef.current?.contains(event.target as Node) || isEditorOverlay(event.target)) return;
      const actionTarget =
        event.target instanceof Element
          ? (event.target.closest(
              'button, a, input, label, [role="button"], .ant-pagination-item, .ant-pagination-prev, .ant-pagination-next',
            ) as HTMLElement | null)
          : null;
      if (!actionTarget) {
        void save();
        return;
      }
      event.preventDefault();
      event.stopPropagation();
      void save().then((saved) => {
        if (!saved) return;
        useEditableCellStore.getState().setReplayingOutsideAction(true);
        actionTarget.click();
        queueMicrotask(() => {
          useEditableCellStore.getState().setReplayingOutsideAction(false);
        });
      });
    };
    document.addEventListener('pointerdown', onPointerDown, true);
    return () => {
      document.removeEventListener('pointerdown', onPointerDown, true);
    };
  }, [editing, save]);

  const beginEdit = async () => {
    if (!enabled || saving) return;
    const { activeCell } = useEditableCellStore.getState();
    if (activeCell && activeCell.id !== idRef.current) {
      const saved = await activeCell.save();
      if (!saved) return;
    }
    useEditableCellStore.getState().setActiveCell({ id: idRef.current, save });
    // 重新进入编辑时清掉上一次的撤销入口
    setUndoMeta(null);
    setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
    setEditing(true);
  };

  const handleUndo = async () => {
    if (!undoMeta) return;
    setUndoMeta(null);
    try {
      await onSave(
        parseValue
          ? parseValue(undoMeta.serializedPrevious)
          : (undoMeta.serializedPrevious as Value),
      );
      message.success('已撤销修改');
    } catch (error) {
      message.error(getErrorMessage(error, '撤销失败'));
    }
  };

  const onPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
    if (event.pointerType !== 'touch' || editing) return;
    touchStartRef.current = {
      pointerId: event.pointerId,
      x: event.clientX,
      y: event.clientY,
    };
  };

  const onPointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
    const touchStart = touchStartRef.current;
    touchStartRef.current = null;
    if (!touchStart || event.pointerType !== 'touch' || event.pointerId !== touchStart.pointerId) {
      return;
    }
    const moved = Math.hypot(event.clientX - touchStart.x, event.clientY - touchStart.y);
    if (moved > 8) {
      lastTouchTapRef.current = null;
      return;
    }

    const now = Date.now();
    const lastTap = lastTouchTapRef.current;
    const isDoubleTap =
      !!lastTap &&
      now - lastTap.time <= 450 &&
      Math.hypot(event.clientX - lastTap.x, event.clientY - lastTap.y) <= 24;
    lastTouchTapRef.current = isDoubleTap
      ? null
      : { time: now, x: event.clientX, y: event.clientY };
    if (isDoubleTap) void beginEdit();
  };

  const onKeyDown = async (event: React.KeyboardEvent) => {
    if (event.key === 'Escape') {
      event.preventDefault();
      cancel();
      return;
    }
    if (event.key === 'Enter' && editor !== 'textarea') {
      // 这些编辑器会自己消费 Enter（确认/提交选中值），不重复触发单元格保存
      if (
        editor === 'select' ||
        editor === 'multi-select' ||
        editor === 'tags' ||
        editor === 'date' ||
        editor === 'date-range'
      ) {
        return;
      }
      event.preventDefault();
      await save();
      return;
    }
    if (event.key === 'Tab') await save();
  };

  const commonProps = {
    autoFocus: true,
    value: draft as never,
    placeholder,
    disabled: saving,
    onChange: (next: unknown) =>
      setDraft(
        next && typeof next === 'object' && 'target' in next
          ? (next as React.ChangeEvent<HTMLInputElement>).target.value
          : next,
      ),
    onKeyDown,
  };

  let control: React.ReactNode;
  if (editor === 'select' || editor === 'multi-select' || editor === 'tags') {
    control = (
      <Select
        {...commonProps}
        onChange={(next) => {
          setDraft(next);
          if (editor === 'select') void saveValue(next);
        }}
        mode={editor === 'multi-select' ? 'multiple' : editor === 'tags' ? 'tags' : undefined}
        options={options}
        open
        popupMatchSelectWidth={false}
        popupClassName="editable-cell-dropdown"
      />
    );
  } else if (editor === 'date') {
    control = <DatePicker {...commonProps} format="YYYY-MM-DD" open />;
  } else if (editor === 'date-range') {
    const { placeholder: _placeholder, ...rangeProps } = commonProps;
    control = <DatePicker.RangePicker {...rangeProps} format="YYYY-MM-DD" open />;
  } else if (editor === 'number' || editor === 'money') {
    control = (
      <InputNumber {...commonProps} min={min} max={max} precision={editor === 'money' ? 2 : 0} />
    );
  } else if (editor === 'textarea') {
    control = <Input.TextArea {...commonProps} autoSize={{ minRows: 1, maxRows: 4 }} />;
  } else {
    control = <Input {...commonProps} />;
  }

  return (
    <div
      ref={rootRef}
      className={`editable-cell${enabled ? ' editable-cell--enabled' : ''}${editing ? ' editable-cell--editing' : ''}`}
      onDoubleClick={() => void beginEdit()}
      onPointerDown={onPointerDown}
      onPointerUp={onPointerUp}
      onPointerCancel={() => {
        touchStartRef.current = null;
        lastTouchTapRef.current = null;
      }}
    >
      {editing ? (
        <Spin spinning={saving}>{control}</Spin>
      ) : (
        <Tooltip title={enabled ? '双击编辑，触屏双击编辑' : undefined}>
          <span className="editable-cell-display">
            {children}
            {undoMeta ? (
              <button
                type="button"
                className="editable-cell-undo"
                onClick={(event) => {
                  event.stopPropagation();
                  void handleUndo();
                }}
              >
                撤销
              </button>
            ) : null}
          </span>
        </Tooltip>
      )}
    </div>
  );
};

export default EditableCell;
