import electron from 'electron';
import type { BrowserWindow } from 'electron';
const { ipcMain, dialog, shell } = electron;
import { join, normalize, sep } from 'node:path';
import type { NoteRepository } from '../repository/NoteRepository.js';
import type { ContinuityService } from '../services/ContinuityService.js';
import type { CaptureService } from '../services/CaptureService.js';
import type { HealthChecker } from '../services/HealthChecker.js';
import type { IntentService } from '../services/IntentService.js';
import type { Note, NoteStatus } from '@shared/types';
import type { HealthResult } from '../ai/InferenceProvider.js';
import { LocalOllamaProvider } from '../ai/LocalOllamaProvider.js';
import { classifyStatus } from '../ai/classifyStatus.js';
import type { SettingsService } from '../services/SettingsService.js';
import type { ProviderHolder } from '../ai/ProviderHolder.js';

export interface InboxIpcDeps {
  repo: NoteRepository;
  continuity: ContinuityService;
  capture: CaptureService;
  health: HealthChecker;
  intent: IntentService;
  getInboxWindow: () => BrowserWindow | null;
  settings: SettingsService;
  providerHolder: ProviderHolder;
  // v0.2.8 Cut A — `inbox:open-media` 의 path traversal 검사 baseline.
  paths: { profileDir: string };
  // v0.2.9 Cut B Task 16 — disabled 메모 일괄 처리 시 in-memory worker queue 갱신.
  // 미주입 시 fire-and-forget skip (다음 launch 의 loadFromDb 가 처리). 본 hook 은
  // AiWorker 인스턴스 직접 주입을 피해 IPC 모듈이 worker import 를 갖지 않도록 분리.
  enqueue?: (noteId: string) => Promise<void>;
}

export function registerInboxApi(deps: InboxIpcDeps): void {
  ipcMain.handle('inbox:list', (_e, opts: { limit: number; cursor?: string }) =>
    deps.repo.list(opts)
  );

  ipcMain.handle(
    'inbox:updateAi',
    (_e, arg: { noteId: string; fields: { title?: string; summary?: string; tags?: string[] } }) => {
      deps.repo.updateUserAiFields(arg.noteId, arg.fields);
    }
  );

  ipcMain.handle('inbox:setDueDate', (_e, arg: { noteId: string; date: string | null }) => {
    deps.repo.setDueDate(arg.noteId, arg.date);
  });

  ipcMain.handle('inbox:trash', async (_e, noteId: string) => {
    await deps.capture.deleteNote(noteId);
  });

  ipcMain.handle(
    'inbox:setIntent',
    (_e, arg: { noteId: string; text: string }) => {
      deps.intent.setIntent(arg.noteId, arg.text);
    }
  );

  ipcMain.handle('inbox:dismissIntent', (_e, noteId: string) => {
    deps.intent.dismissIntent(noteId);
  });

  ipcMain.handle('inbox:continuity', () => deps.continuity.get());
  ipcMain.handle('inbox:pendingCount', () => deps.repo.getPendingCount());
  ipcMain.handle('inbox:ollamaStatus', () => deps.health.lastStatus());
  ipcMain.handle('inbox:todayCount', () => deps.repo.countToday());

  ipcMain.handle('inbox:restore', async (_e, noteId: string) => {
    await deps.capture.restoreNote(noteId);
  });

  ipcMain.handle('inbox:permanentDelete', async (_e, noteId: string) => {
    const win = deps.getInboxWindow();
    const opts: Electron.MessageBoxOptions = {
      type: 'question',
      buttons: ['영구 삭제', '취소'],
      defaultId: 1,
      cancelId: 1,
      title: 'Inkling',
      message: '이 노트를 영구 삭제합니다',
      detail: '이 작업은 되돌릴 수 없습니다. 첨부된 이미지도 함께 삭제됩니다.'
    };
    const r = win
      ? await dialog.showMessageBox(win, opts)
      : await dialog.showMessageBox(opts);
    if (r.response !== 0) return { confirmed: false };
    await deps.capture.permanentDeleteNote(noteId);
    return { confirmed: true };
  });

  ipcMain.handle('inbox:emptyTrash', async () => {
    const fullCount = deps.repo.countTrashed();
    if (fullCount === 0) return { confirmed: true, count: 0 };
    const win = deps.getInboxWindow();
    const opts: Electron.MessageBoxOptions = {
      type: 'question',
      buttons: ['휴지통 비우기', '취소'],
      defaultId: 1,
      cancelId: 1,
      title: 'Inkling',
      message: `휴지통의 노트 ${fullCount}개를 영구 삭제합니다`,
      detail: '이 작업은 되돌릴 수 없습니다. 첨부된 이미지도 함께 삭제됩니다.'
    };
    const r = win
      ? await dialog.showMessageBox(win, opts)
      : await dialog.showMessageBox(opts);
    if (r.response !== 0) return { confirmed: false, count: 0 };
    const result = await deps.capture.emptyTrash();
    return { confirmed: true, count: result.count };
  });

  ipcMain.handle('inbox:listTrash', (_e, opts: { limit: number }) =>
    deps.repo.listTrashed(opts)
  );

  ipcMain.handle('inbox:trashCount', () => deps.repo.countTrashed());

  ipcMain.handle('inbox:listExpired', async () => deps.capture.listExpired());

  ipcMain.handle(
    'inbox:trashExpiredBatch',
    async (_e, payload: { ids: string[] }) => {
      if (payload.ids.length === 0) return { trashedCount: 0, confirmed: false };
      const win = deps.getInboxWindow();
      const opts: Electron.MessageBoxOptions = {
        type: 'question',
        buttons: ['옮기기', '취소'],
        defaultId: 1,
        cancelId: 1,
        title: 'Inkling',
        message: `선택한 노트 ${payload.ids.length}개를 휴지통으로 옮깁니다`,
        detail: '복구는 휴지통 탭에서 가능합니다.'
      };
      const r = win
        ? await dialog.showMessageBox(win, opts)
        : await dialog.showMessageBox(opts);
      if (r.response !== 0) return { trashedCount: 0, confirmed: false };
      const result = await deps.capture.trashExpiredBatch(payload.ids);
      return { trashedCount: result.trashedCount, confirmed: true };
    }
  );

  ipcMain.handle('inbox:ollamaRecheck', async () => {
    await deps.health.runOnce({ manual: true });
    return deps.health.lastStatus();
  });

  ipcMain.handle('inbox:retryAllFailed', async () => deps.capture.retryAllFailed());
  ipcMain.handle('inbox:failedCount', () => deps.repo.countFailed());

  ipcMain.handle('inbox:listRecallCandidate', () => deps.capture.listRecallCandidate());
  ipcMain.handle('inbox:markRecallOpened', (_e, id: string) => deps.capture.markRecallOpened(id));
  ipcMain.handle('inbox:dismissRecall', (_e, id: string) => deps.capture.dismissRecall(id));
  ipcMain.on('inbox:emitRecallShown', (_e, id: string) => { void deps.capture.emitRecallShown(id); });
  ipcMain.on('inbox:emitRecallSnoozed', (_e, id: string) => { void deps.capture.emitRecallSnoozed(id); });

  ipcMain.handle('inbox:loadOllamaSettings', async () => {
    const s = await deps.settings.load();
    return s.ollama ?? null;
  });

  // v0.2.8 Cut A — 첨부 이미지 클릭 시 OS 기본 뷰어로 열기 (Task 3).
  // path traversal 검사는 inkling-media:// protocol handler 와 동일한 패턴 (Task 1).
  ipcMain.handle('inbox:open-media', async (_e, relPath: string) => {
    if (typeof relPath !== 'string' || relPath.length === 0) {
      return { ok: false as const, reason: 'invalid path' as const };
    }
    const profileDir = deps.paths.profileDir;
    const mediaRoot = join(profileDir, 'media');
    const target = normalize(join(profileDir, relPath));
    if (!target.startsWith(mediaRoot + sep) && target !== mediaRoot) {
      return { ok: false as const, reason: 'invalid path' as const };
    }
    await shell.openPath(target);
    return { ok: true as const };
  });

  // v0.2.9 Cut B Task 4 — status 별 노트 목록.
  ipcMain.handle(
    'inbox:list-by-status',
    (_e, status: NoteStatus, opts: { limit?: number } = {}) => {
      const VALID: readonly NoteStatus[] = ['active', 'completed', 'archived', 'trashed'];
      if (!VALID.includes(status)) return [] as Note[];
      return deps.repo.listByStatus(status, opts);
    }
  );

  // v0.2.9 Cut B Task 4 — 4 status counts (헤더 4탭 badge).
  ipcMain.handle('inbox:counts-by-status', () => ({
    active: deps.repo.countByStatus('active'),
    completed: deps.repo.countByStatus('completed'),
    archived: deps.repo.countByStatus('archived'),
    trashed: deps.repo.countByStatus('trashed')
  }));

  // v0.2.9 Cut B Task 8 — status 4분기 직접 전이 (사유 포함).
  // Modal 의 "완료/보관/휴지통" 버튼 path. backward compat 동기화는
  // NoteRepository.setStatus 내부에서 처리 (deleted_at sync).
  ipcMain.handle(
    'inbox:set-status',
    async (_e, id: string, status: NoteStatus, reason: string | null) => {
      const VALID: readonly NoteStatus[] = ['active', 'completed', 'archived', 'trashed'];
      if (!VALID.includes(status)) {
        return { ok: false as const, reason: 'invalid status' as const };
      }
      deps.repo.setStatus(id, status, reason);
      return { ok: true as const };
    }
  );

  // v0.2.9 Cut B Task 9 — AI 자동 분류 (status 추천).
  // Ollama provider.generateRaw 호출 + JSON 응답 파싱. 에러/실패 시 archived fallback
  // (사용자 데이터 보존 우선). 자세한 prompt + parse 로직은 src/main/ai/classifyStatus.ts.
  ipcMain.handle('ai:classify-status', async (_e, id: string, reason: string) => {
    const note = deps.repo.findById(id);
    if (note === null) {
      return {
        recommended: 'archived' as const,
        rationale: '메모를 찾을 수 없음 — 안전하게 보관 추천'
      };
    }
    const provider = deps.providerHolder.get();
    return classifyStatus({
      provider,
      rawText: note.rawText,
      summary: note.aiSummary ?? '',
      reason: typeof reason === 'string' ? reason : ''
    });
  });

  // v0.2.9 Cut B Task 16 — disabled 메모 (ai_enabled OFF 시기 캡처) 일괄 재투입.
  // OFF→ON 전환 후 사용자가 "지금 모두 처리" 버튼 클릭 path. repo.requeueDisabled 가
  // ai_status='pending' + pending_jobs row 보장, worker.enqueue 가 in-memory queue 갱신.
  ipcMain.handle('inbox:enqueue-disabled', async () => {
    // requeue 전 대상 id 수집 — UPDATE 가 status 바꾸므로 select 후 update 필요 없이
    // requeueDisabled 가 처리한 다음 pending_jobs 에서 다시 가져와 enqueue.
    const targets = deps.repo.getAllPendingJobs().map((j) => j.noteId);
    const before = new Set(targets);
    const count = deps.repo.requeueDisabled();
    if (count > 0 && deps.enqueue) {
      const after = deps.repo.getAllPendingJobs();
      // requeue 직후 새로 들어온 pending_jobs row 만 enqueue (기존 row 는 이미 in-memory queue 에).
      for (const j of after) {
        if (!before.has(j.noteId)) {
          await deps.enqueue(j.noteId);
        }
      }
    }
    return { count };
  });

  ipcMain.handle('inbox:get-disabled-count', () => deps.repo.countByAiStatus('disabled'));

  ipcMain.handle('inbox:saveOllamaSettings', async (_e, value: { endpoint: string; model: string }) => {
    // 검증: 새 인스턴스로 healthCheck
    const trial = new LocalOllamaProvider({ endpoint: value.endpoint, model: value.model });
    const r = await trial.healthCheck();
    if (!r.ok) return { ok: false, reason: r.reason ?? 'unknown' };
    try {
      await deps.settings.setOllama(value);
    } catch (e) {
      return { ok: false, reason: `persist failed: ${(e as Error).message}` };
    }
    deps.providerHolder.get().abort?.();
    deps.providerHolder.replace(trial);
    // 즉시 health 재확인 → onUpdate callback 통해 OllamaBanner 자동 갱신
    await deps.health.runOnce();
    return { ok: true };
  });

  // v0.2.10 Cut C — raw_text 가변 + revision 보존.
  // updateRawText: 빈 문자열 reject (trim 후 length===0). 그 외엔 그대로 (newline/space 보존).
  // listRevisions: 그대로 반환 (camelCase 이미 hydrate 됨).
  // restoreRevision: repo throw → { ok: false } (UI 가 에러 표시).
  ipcMain.handle('inbox:update-raw-text', async (_e, id: string, newText: string) => {
    if (typeof newText !== 'string' || newText.trim().length === 0) {
      return { ok: false as const, reason: 'empty' as const };
    }
    deps.repo.updateRawText(id, newText);
    return { ok: true as const };
  });

  ipcMain.handle('inbox:list-revisions', (_e, id: string) => deps.repo.listRevisions(id));

  ipcMain.handle('inbox:restore-revision', async (_e, id: string, revId: number) => {
    try {
      deps.repo.restoreRevision(id, revId);
      return { ok: true as const };
    } catch (e) {
      return { ok: false as const, reason: (e as Error).message };
    }
  });

  // v0.2.11 Cut D — FTS5 검색 + 회고 aggregate.
  ipcMain.handle(
    'inbox:search',
    (_e, query: string, opts: { limit?: number; status?: NoteStatus } = {}) =>
      deps.repo.search(query, opts)
  );

  ipcMain.handle('inbox:review-aggregate', (_e, period: 'daily' | 'weekly' | 'monthly') => {
    const VALID = ['daily', 'weekly', 'monthly'] as const;
    if (!(VALID as readonly string[]).includes(period)) {
      return {
        totalCount: 0,
        recentNotes: [],
        tagCounts: [],
        dueProgress: { total: 0, passed: 0, pending: 0 }
      };
    }
    return deps.repo.reviewAggregate(period);
  });
}

export function pushNoteUpdated(getWin: () => BrowserWindow | null, note: Note): void {
  const w = getWin();
  if (!w || w.isDestroyed()) return;
  w.webContents.send('note:updated', note);
}

export function pushOllamaStatus(getWin: () => BrowserWindow | null, status: HealthResult): void {
  const w = getWin();
  if (!w || w.isDestroyed()) return;
  w.webContents.send('ollama:status', status);
}
