import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { DatePicker, Input, InputNumber, Select, Spin, Tooltip } from 'antd';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import { usePermission } from '../../hooks/usePermission';
import { message } from '../../ui/app-message';
import './style.css';

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>;
}

let activeCell: { id: string; save: () => Promise<boolean> } | null = null;
let replayingOutsideAction = false;

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 JSON.stringify(left ?? null) === JSON.stringify(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 enabled = !disabled && (!permission || hasPermission(permission));

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

  useEffect(() => {
    if (!editing) {
      setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
    }
  }, [editing, editor, formatValue, value]);

  const cancel = useCallback(() => {
    setDraft(normalizeEditableValue(formatValue ? formatValue(value) : value, editor));
    if (activeCell?.id === idRef.current) activeCell = null;
    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)) {
      if (activeCell?.id === idRef.current) activeCell = null;
      setEditing(false);
      return true;
    }
    setSaving(true);
    try {
      await onSave(parseValue ? parseValue(serialized) : (serialized as Value));
      if (activeCell?.id === idRef.current) activeCell = null;
      setEditing(false);
      return true;
    } catch (error) {
      message.error((error as { message?: string })?.message || '保存失败');
      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 && activeCell?.id === cellId) activeCell.save = save;
    return () => {
      if (activeCell?.id === cellId) activeCell = null;
    };
  }, [editing, save]);

  useEffect(() => {
    if (!editing) return;
    const onPointerDown = (event: PointerEvent) => {
      if (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;
        replayingOutsideAction = true;
        actionTarget.click();
        queueMicrotask(() => {
          replayingOutsideAction = false;
        });
      });
    };
    document.addEventListener('pointerdown', onPointerDown, true);
    return () => {
      document.removeEventListener('pointerdown', onPointerDown, true);
    };
  }, [editing, save]);

  const beginEdit = async () => {
    if (!enabled || saving) return;
    if (activeCell && activeCell.id !== idRef.current) {
      const saved = await activeCell.save();
      if (!saved) return;
    }
    activeCell = { id: idRef.current, save };
    setEditing(true);
  };

  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') {
      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}>{children}</Tooltip>
      )}
    </div>
  );
};

export default EditableCell;
