import type { JSX } from 'preact';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { cn } from './cn';
import { getI18n } from '../../core/i18n';

/**
 * List
 * ----
 * Supports both a plain container and a collection with pointer-based sorting.
 */

export type ListProps = JSX.HTMLAttributes<HTMLDivElement>;

type SortDragState = {
  id: string;
  index: number;
  startX: number;
  startY: number;
  x: number;
  y: number;
  active: boolean;
  targetIndex: number;
};

const INTERACTIVE_SORT_IGNORE_SELECTOR =
  'button,a,input,select,textarea,label,[role="button"],[data-ask-sort-ignore="true"]';
const SORT_HANDLE_SELECTOR = '[data-ask-sort-handle="true"]';

function clampIndex(value: number, min: number, max: number): number {
  if (!Number.isFinite(value)) return min;
  return Math.max(min, Math.min(max, value));
}

function moveItem<T>(arr: T[], from: number, to: number): T[] {
  const next = arr.slice();
  const [picked] = next.splice(from, 1);
  next.splice(clampIndex(to, 0, next.length), 0, picked);
  return next;
}

export function List({ className, ...props }: ListProps): JSX.Element;
export function List<T>(props: ListCollectionProps<T>): JSX.Element;

export function List(props: any) {
  const { items } = props as { items?: unknown[] };

  if (!Array.isArray(items)) {
    const { className, ...rest } = props as ListProps;
    return <div className={cn('ask-flex ask-flex-col ask-gap-1', className)} {...rest} />;
  }

  return <CollectionList {...(props as ListCollectionProps<any>)} />;
}

function CollectionList(props: ListCollectionProps<any>) {
  const {
    items: _items,
    renderItem,
    columns = 1,
    gap = 8,
    header,
    footer,
    empty,
    getKey,
    listId,
    sortable,
    className,
    style,
    ...rest
  } = props;

  const rootRef = useRef<HTMLDivElement | null>(null);
  const dragRef = useRef<SortDragState | null>(null);
  const suppressClickRef = useRef(false);
  const [dragState, setDragState] = useState<SortDragState | null>(null);

  const sortableEnabled = Boolean(sortable?.enabled);
  const getItemId = useMemo(() => {
    const fn = sortable?.getId ?? getKey;
    return (item: any, index: number) => String(fn ? fn(item, index) : index);
  }, [sortable?.getId, getKey]);

  const emit = useCallback((name: ListEventName, detail: ListSortEventDetail<any>) => {
    const el = rootRef.current;
    if (!el) return;
    el.dispatchEvent(new CustomEvent(name, { bubbles: true, cancelable: false, detail }));
  }, []);

  const ids = useMemo(() => _items.map((it: any, idx: number) => getItemId(it, idx)), [_items, getItemId]);
  const idsKey = ids.join('|');

  const getSortableElements = useCallback((): HTMLElement[] => {
    const root = rootRef.current;
    if (!root) return [];
    const seen = new Set<string>();
    return Array.from(root.querySelectorAll<HTMLElement>('[data-ask-sort-id]')).filter((el) => {
      const id = String(el.dataset.askSortId ?? '');
      if (!id || seen.has(id)) return false;
      seen.add(id);
      return true;
    });
  }, []);

  const getTargetIndexFromPoint = useCallback((_clientX: number, clientY: number, activeId: string): number => {
    const elements = getSortableElements().filter((el) => String(el.dataset.askSortId ?? '') !== activeId);
    if (!elements.length) return 0;

    const sorted = elements.slice().sort((a, b) => {
      const ar = a.getBoundingClientRect();
      const br = b.getBoundingClientRect();
      const rowDelta = ar.top - br.top;
      if (Math.abs(rowDelta) > 4) return rowDelta;
      return ar.left - br.left;
    });

    for (let index = 0; index < sorted.length; index++) {
      const rect = sorted[index].getBoundingClientRect();
      const mid = rect.top + rect.height / 2;
      if (clientY < mid) return index;
    }

    return sorted.length;
  }, [getSortableElements]);

  const buildDetail = useCallback((fromIndex: number, toIndex: number, nextItems: any[]): ListSortEventDetail<any> => ({
    listId,
    fromIndex,
    toIndex,
    activeId: ids[fromIndex] ?? null,
    overId: ids[toIndex] ?? null,
    items: _items,
    nextItems,
  }), [listId, ids, _items]);

  const finalizeSort = useCallback((fromIndex: number, toIndex: number) => {
    if (!sortableEnabled) return;
    const safeFrom = clampIndex(fromIndex, 0, _items.length - 1);
    const safeTo = clampIndex(toIndex, 0, Math.max(0, _items.length - 1));
    if (safeFrom === safeTo) return;

    const nextItems = moveItem(_items, safeFrom, safeTo);
    const detail = buildDetail(safeFrom, safeTo, nextItems);
    emit('asuka:list:sortEnd', detail);
    sortable?.onSortEnd?.(detail);
    sortable?.onSorted?.(nextItems, detail);
  }, [sortableEnabled, _items, buildDetail, emit, sortable]);

  useEffect(() => {
    const handlePointerMove = (event: PointerEvent) => {
      const current = dragRef.current;
      if (!current) return;

      const dx = event.clientX - current.startX;
      const dy = event.clientY - current.startY;
      const active = current.active || Math.sqrt(dx * dx + dy * dy) >= 5;
      const targetIndex = getTargetIndexFromPoint(event.clientX, event.clientY, current.id);
      const next = { ...current, x: event.clientX, y: event.clientY, active, targetIndex };

      dragRef.current = next;
      setDragState(next);

      if (active) {
        suppressClickRef.current = true;
        try { event.preventDefault(); } catch {}
      }
    };

    const handlePointerUp = (event: PointerEvent) => {
      const current = dragRef.current;
      if (!current) return;

      try { event.preventDefault(); } catch {}
      const finalTargetIndex = getTargetIndexFromPoint(event.clientX, event.clientY, current.id);
      dragRef.current = null;
      setDragState(null);

      if (current.active || suppressClickRef.current) {
        finalizeSort(current.index, finalTargetIndex);
      }

      window.setTimeout(() => {
        suppressClickRef.current = false;
      }, 0);
    };

    const handlePointerCancel = () => {
      dragRef.current = null;
      setDragState(null);
      window.setTimeout(() => {
        suppressClickRef.current = false;
      }, 0);
    };

    window.addEventListener('pointermove', handlePointerMove, { capture: true });
    window.addEventListener('pointerup', handlePointerUp, { capture: true });
    window.addEventListener('pointercancel', handlePointerCancel, { capture: true });

    return () => {
      window.removeEventListener('pointermove', handlePointerMove, { capture: true } as any);
      window.removeEventListener('pointerup', handlePointerUp, { capture: true } as any);
      window.removeEventListener('pointercancel', handlePointerCancel, { capture: true } as any);
    };
  }, [finalizeSort, getTargetIndexFromPoint]);

  useEffect(() => {
    dragRef.current = null;
    setDragState(null);
  }, [idsKey]);

  const gridStyle: JSX.CSSProperties = {
    display: 'grid',
    gridTemplateColumns: `repeat(${Math.max(1, Number(columns) || 1)}, minmax(0, 1fr))`,
    gap,
  };

  const renderSortPlaceholder = (key: string) => (
    <div key={key} className="ask-dnd-list-placeholder" aria-hidden="true" />
  );

  return (
    <div
      ref={rootRef}
      className={cn('ask-flex ask-flex-col ask-gap-2', className)}
      style={style}
      data-ask-list-id={listId}
      {...rest}
    >
      {header ? <div>{header}</div> : null}

      {_items.length === 0 ? (
        empty ? <div>{empty}</div> : <div className="ask-text-[11px] ask-text-textMuted">{getI18n().t('common.noItems', 'No items.')}</div>
      ) : (
        <div style={gridStyle}>
          {_items.map((item, index) => {
            const id = getItemId(item, index);
            const isDragging = dragState?.id === id && dragState.active;
            const showPlaceholderBefore =
              sortableEnabled &&
              dragState?.active &&
              dragState.id !== id &&
              dragState.targetIndex === index;
            const key = String(getKey ? getKey(item, index) : id);

            if (!sortableEnabled) {
              return <div key={key}>{renderItem(item, index)}</div>;
            }

            return (
              <div key={key} style={{ display: 'contents' }}>
                {showPlaceholderBefore ? renderSortPlaceholder(`placeholder-before-${key}`) : null}
                <div
                  data-ask-sort-id={id}
                  onPointerDown={(event: any) => {
                    if (!sortableEnabled) return;
                    if (typeof event?.button === 'number' && event.button !== 0) return;
                    const target = event.target as HTMLElement | null;
                    const explicitHandle = target?.closest?.(SORT_HANDLE_SELECTOR);
                    const interactive = target?.closest?.(INTERACTIVE_SORT_IGNORE_SELECTOR);
                    if (interactive && !explicitHandle) return;

                    try { event.currentTarget?.setPointerCapture?.(event.pointerId); } catch {}

                    const next: SortDragState = {
                      id,
                      index,
                      startX: event.clientX,
                      startY: event.clientY,
                      x: event.clientX,
                      y: event.clientY,
                      active: false,
                      targetIndex: index,
                    };
                    dragRef.current = next;
                    setDragState(next);

                    const detail = buildDetail(index, index, _items);
                    emit('asuka:list:sortStart', detail);
                    sortable?.onSortStart?.(detail);
                  }}
                  onClickCapture={(event: any) => {
                    if (!suppressClickRef.current) return;
                    try { event.preventDefault?.(); } catch {}
                    try { event.stopPropagation?.(); } catch {}
                  }}
                  className={cn(
                    'ask-rounded-[12px] ask-touch-none ask-select-none',
                    isDragging && 'ask-dnd-sort-item--dragging',
                    showPlaceholderBefore && 'ask-dnd-sort-item--over',
                  )}
                >
                  {renderItem(item, index)}
                </div>
              </div>
            );
          })}
          {sortableEnabled && dragState?.active && dragState.targetIndex >= _items.length
            ? renderSortPlaceholder('placeholder-after-last')
            : null}
        </div>
      )}

      {dragState?.active ? (
        <div
          className="ask-dnd-ghost"
          style={{ left: `${dragState.x + 14}px`, top: `${dragState.y + 14}px` }}
        >
          <span>{getI18n().t('common.reorder', 'Reorder')}</span>
          <span className="ask-dnd-ghost__hint">{getI18n().t('common.dropHere', 'Drop here')}</span>
        </div>
      ) : null}

      {footer ? <div>{footer}</div> : null}
    </div>
  );
}

export type ListCollectionProps<T> = Omit<JSX.HTMLAttributes<HTMLDivElement>, 'children'> & {
  items: T[];
  renderItem: (item: T, index: number) => JSX.Element;
  columns?: number;
  gap?: number;
  header?: JSX.Element;
  footer?: JSX.Element;
  empty?: JSX.Element;
  getKey?: (item: T, index: number) => string | number;
  listId?: string;
  sortable?: SortableListConfig<T>;
};

export type ListEventName = 'asuka:list:sortStart' | 'asuka:list:sortEnd';

export type ListSortEventDetail<T> = {
  listId?: string;
  fromIndex: number;
  toIndex: number;
  activeId: string | null;
  overId: string | null;
  items: T[];
  nextItems: T[];
};

export type SortableListConfig<T> = {
  enabled: boolean;
  getId?: (item: T, index: number) => string | number;
  onSortStart?: (detail: ListSortEventDetail<T>) => void;
  onSortEnd?: (detail: ListSortEventDetail<T>) => void;
  onSorted?: (nextItems: T[], detail: ListSortEventDetail<T>) => void;
};

export type ListItemProps = JSX.HTMLAttributes<HTMLDivElement> & {
  active?: boolean;
  disabled?: boolean;
  left?: JSX.Element;
  right?: JSX.Element;
};

export function ListItem({ active, disabled, left, right, className, children, ...props }: ListItemProps) {
  return (
    <div
      className={cn(
        'ask-flex ask-items-center ask-gap-2 ask-rounded-[14px] ask-border ask-border-borderSubtle ask-bg-surface ask-px-3 ask-py-2 ask-text-[12px]',
        active && 'ask-border-primary/40 ask-bg-primary/5',
        disabled && 'ask-opacity-50',
        className,
      )}
      {...props}
    >
      {left ? <div className="ask-shrink-0">{left}</div> : null}
      <div className="ask-min-w-0 ask-flex-1">{children}</div>
      {right ? <div className="ask-shrink-0">{right}</div> : null}
    </div>
  );
}

export default List;
