import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import {
  DndContext,
  KeyboardSensor,
  MeasuringStrategy,
  PointerSensor,
  closestCenter,
  useSensor,
  useSensors,
  type DragEndEvent,
  type DragMoveEvent,
  type DragOverEvent,
  type DragStartEvent,
} from '@dnd-kit/core';
import {
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import classNames from 'classnames';
import MediaInsert from '@ablogcms/media/components/media-insert/media-insert';
import MediaDropArea from '@ablogcms/media/components/media-droparea/media-droparea';
import type { MediaItem, MediaType } from '@ablogcms/media/types';
import { useMediaSelect } from '@ablogcms/react-hooks/use-media';
import { Tooltip } from '@ablogcms/components/tooltip';
import { Button } from '@ablogcms/components/button';
import { Checkbox } from '@ablogcms/components/checkbox';
import DraggableButton from '@ablogcms/components/draggable-button';
import { filterVisibleItems, getProjection, useNestableEdit, type BaseNestableItem } from './hooks';

/**
 * ドラッグ中の水平オフセットを depth 1 段分とみなす基準幅。
 * dnd-kit 公式 Sortable Tree 例に合わせた値。
 */
const INDENTATION_WIDTH = 40;

interface NavigationItem extends BaseNestableItem {
  navigation_label: string;
  navigation_uri: string;
  navigation_attr: string;
  navigation_a_attr: string;
  navigation_target: boolean;
  navigation_publish: boolean;
  navigation_media: MediaItem['media_id'] | null;
  navigation_media_type: MediaType;
  navigation_media_thumbnail: string;
  toggle: boolean;
  hide: boolean;
}

interface NavigationEditorProps {
  items: NavigationItem[];
  enableMedia?: boolean;
  message: {
    open: string;
    label: string;
    detail: string;
    add: string;
    remove: string;
    attr: string;
    child_attr: string;
  };
}

interface NavigationEditorRowProps {
  item: NavigationItem;
  depth: number;
  hasChildren: boolean;
  isCollapsed: boolean;
  onToggleCollapse: () => void;
  message: NavigationEditorProps['message'];
  enableMedia?: boolean;
  updateItem: <K extends keyof NavigationItem>(id: number, key: K, value: NavigationItem[K]) => void;
  addChild: (item: NavigationItem) => void;
  removeItem: (id: number, confirmMessage: string) => void;
  onMediaChange: (media: MediaItem | MediaItem[] | null) => void;
  onSelectMediaClick: () => void;
}

function NavigationEditorRow({
  item,
  depth,
  hasChildren,
  isCollapsed,
  onToggleCollapse,
  message,
  enableMedia,
  updateItem,
  addChild,
  removeItem,
  onMediaChange,
  onSelectMediaClick,
}: NavigationEditorRowProps) {
  const { setNodeRef, setActivatorNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({
    id: item.id,
  });

  const style: React.CSSProperties = {
    transform: CSS.Translate.toString(transform),
    transition,
    paddingLeft: depth * INDENTATION_WIDTH,
  };

  return (
    <div
      ref={setNodeRef}
      style={style}
      className={classNames('acms-admin-form', {
        'acms-admin-nested-private': !item.navigation_publish,
        'acms-admin-nested-dragging': isDragging,
      })}
      aria-label={`ナビゲーション項目 ${item.navigation_label || '未設定'}。ドラッグで並び替え可能です。`}
    >
      <div className="acms-admin-nested-item">
        <div className="acms-admin-nested-item-header clearfix">
          <div className="acms-admin-nested-item-inner">
            <div className="acms-admin-nested-item-handle">
              <DraggableButton ref={setActivatorNodeRef} size="small" {...attributes} {...listeners} />
            </div>
            <div className="acms-admin-nested-item-child acms-admin-nested-item-child-checkbox">
              <Checkbox
                id={`navigation-item-publish-${item.uuid}`}
                defaultChecked={item.navigation_publish}
                value="on"
                label={message.open}
                onChange={(event) => {
                  event.stopPropagation();
                  updateItem(item.id, 'navigation_publish', event.target.checked);
                }}
              />
            </div>
            <div className="acms-admin-nested-item-child">
              <div className="acms-admin-form-input-group">
                <label
                  className="acms-admin-form-side"
                  style={{ whiteSpace: 'nowrap' }}
                  htmlFor={`navigation-item-label-${item.uuid}`}
                >
                  {message.label}
                </label>
                <input
                  type="text"
                  className="acms-admin-form-width-full"
                  id={`navigation-item-label-${item.uuid}`}
                  defaultValue={item.navigation_label}
                  aria-required="true"
                  onChange={(event) => {
                    updateItem(item.id, 'navigation_label', event.target.value);
                  }}
                />
              </div>
            </div>
            <div className="acms-admin-nested-item-child acms-admin-nested-item-child-link">
              <div className="acms-admin-form-input-group">
                <label
                  className="acms-admin-form-side"
                  style={{ whiteSpace: 'nowrap' }}
                  htmlFor={`navigation-item-url-${item.uuid}`}
                >
                  URL
                </label>
                <input
                  type="text"
                  placeholder="https://example.com/"
                  className="acms-admin-nested-input"
                  id={`navigation-item-url-${item.uuid}`}
                  defaultValue={item.navigation_uri}
                  aria-required="true"
                  onChange={(event) => {
                    updateItem(item.id, 'navigation_uri', event.target.value);
                  }}
                />
              </div>
            </div>
            <div className="acms-admin-nested-item-actions">
              {/* acms-admin-btn-admin は Button の variant 合成モデルとは別の独立クラス体系のため unstyled + className で再現する */}
              {hasChildren && (
                <Button
                  unstyled
                  aria-expanded={!isCollapsed}
                  aria-controls={`detail-${item.uuid}`}
                  data-tooltip-id="navigation-tooltip"
                  data-tooltip-variant="dark"
                  data-tooltip-place="top"
                  data-tooltip-content={ACMS.i18n('navigation.show_child_items')}
                  className="acms-admin-btn-admin acms-admin-nested-collapse-btn"
                  onClick={onToggleCollapse}
                >
                  <span
                    className={classNames(
                      'nestable-icon',
                      isCollapsed ? 'nestable-icon--plus-gray' : 'nestable-icon--minus-gray'
                    )}
                  />
                </Button>
              )}
              <Button
                unstyled
                aria-expanded={item.toggle}
                aria-controls={`detail-${item.uuid}`}
                data-tooltip-id="navigation-tooltip"
                data-tooltip-content={ACMS.i18n('navigation.show_details')}
                data-tooltip-variant="dark"
                data-tooltip-place="top"
                className="acms-admin-btn-admin"
                onClick={() => updateItem(item.id, 'toggle', !item.toggle)}
              >
                {message.detail}
              </Button>
              <Button
                unstyled
                data-tooltip-id="navigation-tooltip"
                data-tooltip-content={ACMS.i18n('navigation.add_new_item')}
                data-tooltip-variant="dark"
                data-tooltip-place="top"
                onClick={() => addChild(item)}
                className="acms-admin-btn-admin"
              >
                {message.add}
              </Button>
              <Button variant="danger" onClick={() => removeItem(item.id, ACMS.i18n('navigation.on_remove'))}>
                {message.remove}
              </Button>
            </div>
          </div>
        </div>
        {item.toggle && (
          <div
            className="acms-admin-nested-item-detail"
            id={`detail-${item.uuid}`}
            aria-hidden={!item.toggle}
            hidden={!item.toggle}
          >
            <div className="acms-admin-nested-item-inner acms-admin-justify-content-start">
              <div className="acms-admin-nested-item-child acms-admin-flex-column acms-admin-gap-2">
                <div className="acms-admin-form-input-group">
                  <label
                    className="acms-admin-form-side"
                    htmlFor={`navigation-item-attr-${item.id}`}
                    style={{ whiteSpace: 'nowrap' }}
                  >
                    {message.attr}
                  </label>
                  <input
                    type="text"
                    className="acms-admin-nested-input"
                    id={`navigation-item-attr-${item.id}`}
                    defaultValue={item.navigation_attr}
                    onChange={(event) => {
                      updateItem(item.id, 'navigation_attr', event.target.value);
                    }}
                  />
                </div>
                <div className="acms-admin-form-input-group">
                  <label
                    className="acms-admin-form-side"
                    htmlFor={`navigation-item-child_attr-${item.id}`}
                    style={{ whiteSpace: 'nowrap' }}
                  >
                    {message.child_attr}
                  </label>
                  <input
                    type="text"
                    className="acms-admin-nested-input"
                    id={`navigation-item-child_attr-${item.id}`}
                    defaultValue={item.navigation_a_attr}
                    onChange={(event) => {
                      updateItem(item.id, 'navigation_a_attr', event.target.value);
                    }}
                  />
                </div>
                <div className="acms-admin-nested-item-child acms-admin-nested-item-child-checkbox">
                  <Checkbox
                    id={`navigation-item-target-${item.id}`}
                    defaultChecked={item.navigation_target}
                    value="_blank"
                    label="別タブで開く"
                    onChange={(event) => {
                      event.stopPropagation();
                      updateItem(item.id, 'navigation_target', event.target.checked);
                    }}
                  />
                </div>
                {enableMedia && (
                  <div className="acms-admin-nested-item-media">
                    <div>
                      <MediaDropArea
                        mid={item.navigation_media ?? undefined}
                        thumbnail={item.navigation_media_thumbnail}
                        mediaType={item.navigation_media_type}
                        accept="image"
                        width={180}
                        height={140}
                        caption=""
                        onChange={onMediaChange}
                        onError={() => {}}
                      />
                    </div>
                    <Button unstyled onClick={onSelectMediaClick} className="acms-admin-btn-admin">
                      メディアから選択
                    </Button>
                  </div>
                )}
              </div>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

export function NavigationEditor({ items: initialItems, enableMedia, message }: NavigationEditorProps) {
  const defineDefaultItem = (): Omit<NavigationItem, keyof BaseNestableItem> => ({
    navigation_label: '',
    navigation_uri: '',
    navigation_attr: '',
    navigation_a_attr: '',
    navigation_target: false,
    navigation_publish: true,
    navigation_media: null,
    navigation_media_type: '' as MediaType,
    navigation_media_thumbnail: '',
    toggle: false,
    hide: true,
  });

  const {
    items,
    setItems,
    updateItem,
    addChild,
    removeItem,
    flattenedItems,
    collapsedIds,
    toggleCollapse,
    moveTo,
    reIndex,
  } = useNestableEdit<NavigationItem>(initialItems, defineDefaultItem);

  const [isOpenAll, setIsOpenAll] = useState<boolean>(false);
  const { isInsertModalOpen, tab, files, handleModalClose, handleSelectClick } = useMediaSelect();
  const [currentItem, setCurrentItem] = useState<NavigationItem | null>(null);

  const [activeId, setActiveId] = useState<number | null>(null);
  const [overId, setOverId] = useState<number | null>(null);
  const [offsetLeft, setOffsetLeft] = useState<number>(0);

  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
  );

  const handleOpenAll = () => {
    setItems(items.map((item) => ({ ...item, toggle: true })));
    setIsOpenAll(true);
  };

  const handleCloseAll = () => {
    setItems(items.map((item) => ({ ...item, toggle: false })));
    setIsOpenAll(false);
  };

  const handleInsertMedia = useCallback(
    (newMedia: MediaItem | MediaItem[] | null, item: NavigationItem | null) => {
      const media = Array.isArray(newMedia) ? newMedia[0] : newMedia;
      if (!item) {
        handleModalClose();
        return;
      }
      setItems(
        items.map((data) =>
          item.id === data.id
            ? {
                ...data,
                navigation_media: media?.media_id ?? null,
                navigation_media_type: media?.media_type ?? ('' as MediaType),
                navigation_media_thumbnail: media?.media_thumbnail ?? '',
              }
            : data
        )
      );
      handleModalClose();
    },
    [items, setItems, handleModalClose]
  );

  // ドラッグ中は自分の子孫を巻き込まないよう、activeId も折りたたみ扱いにして非表示にする
  const visibleItems = useMemo(() => {
    const excludedIds = activeId != null ? new Set([...collapsedIds, activeId]) : collapsedIds;
    return filterVisibleItems(flattenedItems, excludedIds);
  }, [flattenedItems, collapsedIds, activeId]);

  const projected = useMemo(
    () =>
      activeId != null && overId != null
        ? getProjection(visibleItems, activeId, overId, offsetLeft, INDENTATION_WIDTH)
        : null,
    [visibleItems, activeId, overId, offsetLeft]
  );

  const sortedIds = useMemo(() => visibleItems.map((item) => item.id), [visibleItems]);

  const resetDragState = useCallback(() => {
    setActiveId(null);
    setOverId(null);
    setOffsetLeft(0);
  }, []);

  const handleDragStart = useCallback((event: DragStartEvent) => {
    const id = event.active.id as number;
    setActiveId(id);
    setOverId(id);
  }, []);

  const handleDragMove = useCallback((event: DragMoveEvent) => {
    setOffsetLeft(event.delta.x);
  }, []);

  const handleDragOver = useCallback((event: DragOverEvent) => {
    setOverId((event.over?.id as number | undefined) ?? null);
  }, []);

  const handleDragEnd = useCallback(
    (event: DragEndEvent) => {
      const { active, over } = event;
      if (projected && over) {
        moveTo(active.id as number, over.id as number, offsetLeft, INDENTATION_WIDTH);
      }
      resetDragState();
    },
    [projected, offsetLeft, moveTo, resetDragState]
  );

  useEffect(() => {
    const listener = (event: Event) => {
      if (!(event instanceof CustomEvent)) {
        return;
      }
      const media = event.detail.data as MediaItem;
      const updatedItems = items.filter((item) => item.navigation_media === media.media_id);
      const updatedItemIds = updatedItems.map((item) => item.id);
      setItems(
        items.map((data) =>
          updatedItemIds.includes(data.id)
            ? {
                ...data,
                navigation_media: media?.media_id ?? null,
                navigation_media_type: media?.media_type ?? ('' as MediaType),
                navigation_media_thumbnail: media?.media_thumbnail ?? '',
              }
            : data
        )
      );
    };
    document.addEventListener('acms.media-updated', listener);
    return () => {
      document.removeEventListener('acms.media-updated', listener);
    };
  }, [items, setItems]);

  return (
    <div>
      <div className="clearfix" style={{ paddingBottom: '10px' }}>
        {isOpenAll ? (
          <Button className="acms-admin-float-right" onClick={handleCloseAll} aria-pressed="true">
            {ACMS.i18n('navigation.hide_all_details')}
          </Button>
        ) : (
          <Button className="acms-admin-float-right" onClick={handleOpenAll} aria-pressed="false">
            {ACMS.i18n('navigation.show_all_details')}
          </Button>
        )}
      </div>
      <div className="acms-admin-nested-items">
        <DndContext
          sensors={sensors}
          collisionDetection={closestCenter}
          measuring={{ droppable: { strategy: MeasuringStrategy.Always } }}
          onDragStart={handleDragStart}
          onDragMove={handleDragMove}
          onDragOver={handleDragOver}
          onDragEnd={handleDragEnd}
          onDragCancel={resetDragState}
        >
          <SortableContext items={sortedIds} strategy={verticalListSortingStrategy}>
            {visibleItems.map((item) => (
              <NavigationEditorRow
                key={item.uuid}
                item={item}
                depth={item.id === activeId && projected ? projected.depth : item.depth}
                hasChildren={flattenedItems.some((candidate) => candidate.parent === item.id)}
                isCollapsed={collapsedIds.has(item.id)}
                onToggleCollapse={() => toggleCollapse(item.id)}
                message={message}
                enableMedia={enableMedia}
                updateItem={updateItem}
                addChild={addChild}
                removeItem={removeItem}
                onMediaChange={(media) => handleInsertMedia(media, item)}
                onSelectMediaClick={() => {
                  setCurrentItem(item);
                  handleSelectClick();
                }}
              />
            ))}
          </SortableContext>
        </DndContext>
      </div>
      {reIndex.map((item) => (
        <Fragment key={item.id}>
          <input type="hidden" name="navigation_parent[]" value={item.parent ? item.parent : ''} />
          <input type="hidden" name="navigation_label[]" value={item.navigation_label ? item.navigation_label : ''} />
          <input type="hidden" name="navigation_attr[]" value={item.navigation_attr ? item.navigation_attr : ''} />
          <input
            type="hidden"
            name="navigation_a_attr[]"
            value={item.navigation_a_attr ? item.navigation_a_attr : ''}
          />
          <input type="hidden" name="navigation_uri[]" value={item.navigation_uri ? item.navigation_uri : ''} />
          <input type="hidden" name="navigation_publish[]" value={item.navigation_publish ? 'on' : 'off'} />
          <input type="hidden" name="navigation_target[]" value={item.navigation_target ? '_blank' : ''} />
          <input type="hidden" name="navigation_media[]" value={item.navigation_media ? item.navigation_media : ''} />
        </Fragment>
      ))}
      <input type="hidden" name="config[]" value="navigation_publish" />
      <input type="hidden" name="config[]" value="navigation_target" />
      <input type="hidden" name="config[]" value="navigation@sort" />
      <input type="hidden" name="config[]" value="navigation_label" />
      <input type="hidden" name="config[]" value="navigation_uri" />
      <input type="hidden" name="config[]" value="navigation_attr" />
      <input type="hidden" name="config[]" value="navigation_a_attr" />
      <input type="hidden" name="config[]" value="navigation_parent" />
      <input type="hidden" name="config[]" value="navigation_media" />

      <MediaInsert
        isOpen={isInsertModalOpen}
        onInsert={(media) => handleInsertMedia(media, currentItem)}
        onClose={handleModalClose}
        tab={tab}
        radioMode
        files={files}
        filetype="image"
      />

      <Tooltip id="navigation-tooltip" />
    </div>
  );
}

export default NavigationEditor;
