import { act, renderHook } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { getDataviewActionRegistry } from '@ablogcms/plugins/core';
import useEntryAdminActions, { type GetActionsContext } from './use-entry-admin-actions';
import type { EntryType } from '../types';
import { duplicateEntry, trashEntry } from '../api';

vi.mock('../api', () => ({
  duplicateEntry: vi.fn(async () => undefined),
  trashEntry: vi.fn(async () => undefined),
  unlockEntry: vi.fn(async () => undefined),
}));

const createContext = (): GetActionsContext => ({
  invalidate: vi.fn(),
});

const createEntry = (): EntryType => ({ id: 1, title: '対象エントリー' }) as EntryType;

describe('useEntryAdminActions', () => {
  it('ハンドラ0件でもコア定義のアクションがそのまま getActions に渡る（特徴付けテスト）', () => {
    let received: string[] | undefined;
    renderHook(() =>
      useEntryAdminActions({
        getActions: (actions) => {
          received = actions.map((a) => a.id);
          return actions;
        },
        context: createContext(),
      })
    );

    expect(received).toEqual(['edit', 'approval-history', 'duplicate', 'trash', 'unlock', 'lock']);
  });

  describe('ctx.dataview.registerAction("entry", ...) レジストリとのマージ', () => {
    afterEach(() => {
      getDataviewActionRegistry().remove('entry', 'plugin-action');
    });

    it('レジストリに登録したアクションがコア定義の後ろに追加され getActions に渡る', () => {
      getDataviewActionRegistry().add('entry', 'plugin-action', {
        id: 'plugin-action',
        label: 'プラグインアクション',
      });
      let received: string[] | undefined;

      renderHook(() =>
        useEntryAdminActions({
          getActions: (actions) => {
            received = actions.map((a) => a.id);
            return actions;
          },
          context: createContext(),
        })
      );

      expect(received).toContain('plugin-action');
      expect(received?.at(-1)).toBe('plugin-action');
    });

    it('レジストリ登録後もレガシー getActions（フィルタ）が最終結果に適用される', () => {
      getDataviewActionRegistry().add('entry', 'plugin-action', {
        id: 'plugin-action',
        label: 'プラグインアクション',
      });

      const { result } = renderHook(() =>
        useEntryAdminActions({
          getActions: (actions) => actions.filter((a) => a.id !== 'plugin-action'),
          context: createContext(),
        })
      );

      expect(result.current.map((a) => a.id)).not.toContain('plugin-action');
    });
  });

  describe('entry.trash.before / entry.trash.success', () => {
    afterEach(() => {
      const emit = window.ACMS.events.emit as ReturnType<typeof vi.fn>;
      emit.mockClear();
      emit.mockImplementation(async (_type: string, payload: unknown) => ({ canceled: false, event: payload }));
      vi.mocked(trashEntry).mockClear();
      vi.mocked(duplicateEntry).mockClear();
    });

    const callOnAction = async (actionId: string, entry: EntryType) => {
      const { result } = renderHook(() =>
        useEntryAdminActions({ getActions: (actions) => actions, context: createContext() })
      );
      const action = result.current.find((a) => a.id === actionId) as unknown as {
        onAction: (data: EntryType) => Promise<void>;
      };
      await act(async () => {
        await action.onAction(entry);
      });
    };

    it('confirm 承認後、trashEntry の前後で entry.trash.before → entry.trash.success の順に発火する', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
      const entry = createEntry();

      await callOnAction('trash', entry);

      const emit = window.ACMS.events.emit as ReturnType<typeof vi.fn>;
      expect(emit).toHaveBeenNthCalledWith(1, 'entry.trash.before', { entry });
      expect(trashEntry).toHaveBeenCalledWith(entry);
      expect(emit).toHaveBeenNthCalledWith(2, 'entry.trash.success', { entry });
      confirmSpy.mockRestore();
    });

    it('confirm をキャンセルした場合は entry.trash.before も trashEntry も発火しない（既存挙動を維持）', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false);

      await callOnAction('trash', createEntry());

      expect(window.ACMS.events.emit).not.toHaveBeenCalled();
      expect(trashEntry).not.toHaveBeenCalled();
      confirmSpy.mockRestore();
    });

    it('entry.trash.before がキャンセルされた場合は trashEntry を呼ばない', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
      (window.ACMS.events.emit as ReturnType<typeof vi.fn>).mockImplementation(async () => ({
        canceled: true,
        reason: 'blocked',
        event: {},
      }));

      await callOnAction('trash', createEntry());

      expect(trashEntry).not.toHaveBeenCalled();
      confirmSpy.mockRestore();
    });

    it('entry.trash.before ハンドラが entry を差し替えた場合、差し替え後の entry が実際の削除対象になる（HOOKS.md 記載の値変更仕様）', async () => {
      const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
      const replacedEntry = createEntry();
      replacedEntry.id = 999;
      (window.ACMS.events.emit as ReturnType<typeof vi.fn>).mockImplementation(async () => ({
        canceled: false,
        event: { entry: replacedEntry },
      }));

      await callOnAction('trash', createEntry());

      expect(trashEntry).toHaveBeenCalledWith(replacedEntry);
      const emit = window.ACMS.events.emit as ReturnType<typeof vi.fn>;
      expect(emit).toHaveBeenNthCalledWith(2, 'entry.trash.success', { entry: replacedEntry });
      confirmSpy.mockRestore();
    });
  });

  describe('entry.duplicate.success', () => {
    it('duplicateEntry 成功後に entry.duplicate.success を発火する', async () => {
      const entry = createEntry();
      const { result } = renderHook(() =>
        useEntryAdminActions({ getActions: (actions) => actions, context: createContext() })
      );
      const duplicateAction = result.current.find((a) => a.id === 'duplicate') as unknown as {
        onAction: (data: EntryType) => Promise<void>;
      };

      await act(async () => {
        await duplicateAction.onAction(entry);
      });

      expect(duplicateEntry).toHaveBeenCalledWith(entry);
      expect(window.ACMS.events.emit).toHaveBeenCalledWith('entry.duplicate.success', { entry });
    });
  });
});
