import { create } from 'zustand';
import type { Note, ReviewAggregate, WeeklyContinuity } from '@shared/types';
import { inboxApi } from './api.js';
import { nextKstMidnightMs } from '@shared/util/kstDate.js';

export { selectFilteredNotes } from './selectFilteredNotes.js';

// v0.2.9 Cut B Task 4 — 4탭 view enum + settings.
// 'inbox' = active, 'completed'/'archived' = NoteStatus 그대로, 'trash' = trashed (mirror), 'settings' = SettingsPage.
export type InboxView =
  | 'inbox' | 'completed' | 'archived' | 'trash' | 'settings'
  | 'review-daily' | 'review-weekly' | 'review-monthly';

export interface InboxCounts {
  active: number;
  completed: number;
  archived: number;
  trashed: number;
}

interface InboxState {
  notes: Note[];
  trashNotes: Note[];
  trashCount: number;
  showTrash: boolean;
  showSettings: boolean;
  // v0.2.9 Cut B Task 4 — view enum + counts. showTrash/showSettings 는 mirror 로 잠시 잔류.
  view: InboxView;
  counts: InboxCounts;
  continuity: WeeklyContinuity;
  pendingCount: number;
  ollamaStatus: { ok: boolean; reason?: string };
  todayCount: number;
  loading: boolean;
  tagFilter: string | null;
  expiredCandidates: Note[];
  expiredSnoozeUntilMs: number | null;
  failedCount: number;
  recallCandidate: Note | null;
  recallSnoozeUntilMs: number | null;
  // v0.2.9 Cut B Task 14 — AI 비활성 모드에서는 OllamaBanner/FailedBanner render skip.
  // 기본 true (기존 사용자 무영향). loadInitial / refreshMeta 가 settings 로드.
  ai_enabled: boolean;
  // v0.2.11 Cut D — FTS5 search + review aggregate state.
  searchQuery: string;
  searchResults: Note[] | null;  // null = 검색 안 한 상태
  reviewData: ReviewAggregate | null;
  loadInitial: () => Promise<void>;
  refreshMeta: () => Promise<void>;
  upsertNote: (note: Note) => void;
  removeNote: (id: string) => void;
  setTagFilter: (tag: string | null) => void;
  setShowSettings: (open: boolean) => void;
  setView: (view: InboxView) => void;
  loadByView: (view: 'completed' | 'archived' | 'trash') => Promise<void>;
  toggleShowTrash: () => Promise<void>;
  loadTrash: () => Promise<void>;
  restoreNote: (id: string) => Promise<void>;
  permanentDeleteNote: (id: string) => Promise<void>;
  emptyTrash: () => Promise<void>;
  trashExpiredBatch: (ids: string[]) => Promise<void>;
  snoozeExpired: () => void;
  recheckOllama: () => Promise<void>;
  retryAllFailed: () => Promise<void>;
  loadRecallCandidate: () => Promise<void>;
  openRecall: (id: string) => Promise<void>;
  dismissRecallNote: (id: string) => Promise<void>;
  snoozeRecall: () => Promise<void>;
  // v0.2.11 Cut D — search + review actions.
  setSearchQuery: (q: string) => void;
  searchNotes: (q: string) => Promise<void>;
  clearSearch: () => void;
  loadReview: (period: 'daily' | 'weekly' | 'monthly') => Promise<void>;
}

const emptyContinuity: WeeklyContinuity = {
  weekStart: '', weekCount: 0, weekTarget: 7,
  consecutiveCompleteWeeks: 0, showRecoveryToast: false, lastNoteAt: null
};

export const useInbox = create<InboxState>((set, get) => ({
  notes: [],
  trashNotes: [],
  trashCount: 0,
  showTrash: false,
  showSettings: false,
  view: 'inbox',
  counts: { active: 0, completed: 0, archived: 0, trashed: 0 },
  continuity: emptyContinuity,
  pendingCount: 0,
  ollamaStatus: { ok: true },
  todayCount: 0,
  loading: false,
  tagFilter: null,
  expiredCandidates: [],
  expiredSnoozeUntilMs: null,
  failedCount: 0,
  recallCandidate: null,
  recallSnoozeUntilMs: null,
  ai_enabled: true,
  searchQuery: '',
  searchResults: null,
  reviewData: null,
  async loadInitial() {
    set({ loading: true });
    const [notes, continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates, failedCount, recallCandidate, counts, settings] = await Promise.all([
      inboxApi.listNotes({ limit: 50 }),
      inboxApi.getContinuity(),
      inboxApi.getPendingCount(),
      inboxApi.getOllamaStatus(),
      inboxApi.getTodayCount(),
      inboxApi.getTrashCount(),
      inboxApi.listExpired(),
      inboxApi.getFailedCount(),
      inboxApi.listRecallCandidate(),
      inboxApi.countsByStatus(),
      inboxApi.getSettings()
    ]);
    set({ notes, continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates, failedCount, recallCandidate, counts, ai_enabled: settings.ai_enabled ?? true, loading: false });
  },
  async refreshMeta() {
    const [continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates, failedCount, recallCandidate, counts, settings] = await Promise.all([
      inboxApi.getContinuity(),
      inboxApi.getPendingCount(),
      inboxApi.getOllamaStatus(),
      inboxApi.getTodayCount(),
      inboxApi.getTrashCount(),
      inboxApi.listExpired(),
      inboxApi.getFailedCount(),
      inboxApi.listRecallCandidate(),
      inboxApi.countsByStatus(),
      inboxApi.getSettings()
    ]);
    set({ continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates, failedCount, recallCandidate, counts, ai_enabled: settings.ai_enabled ?? true });
  },
  upsertNote(note) {
    // trashCount 는 server-authoritative. trashNotes 가 cache-loaded (showTrash=true) 일
    // 때만 trashCount 를 local recompute. 그 외엔 server 값 (refreshMeta) 보존.
    const showTrash = get().showTrash;
    if (note.deletedAt !== null) {
      // trash 노트: notes 에서 제거 + trashNotes 에 upsert
      const cleanNotes = get().notes.filter((n) => n.id !== note.id);
      const ti = get().trashNotes.findIndex((n) => n.id === note.id);
      const nextTrash = get().trashNotes.slice();
      if (ti >= 0) nextTrash[ti] = note;
      else nextTrash.unshift(note);
      set({
        notes: cleanNotes,
        trashNotes: nextTrash,
        ...(showTrash ? { trashCount: nextTrash.length } : {})
      });
    } else {
      // active 노트: trashNotes 에서 제거 + notes 에 upsert (restore 케이스 포함)
      const cleanTrash = get().trashNotes.filter((n) => n.id !== note.id);
      const i = get().notes.findIndex((n) => n.id === note.id);
      const nextNotes = get().notes.slice();
      if (i >= 0) nextNotes[i] = note;
      else nextNotes.unshift(note);
      set({
        notes: nextNotes,
        trashNotes: cleanTrash,
        ...(showTrash ? { trashCount: cleanTrash.length } : {})
      });
    }
  },
  removeNote(id) {
    const cleanNotes = get().notes.filter((n) => n.id !== id);
    const cleanTrash = get().trashNotes.filter((n) => n.id !== id);
    const showTrash = get().showTrash;
    set({
      notes: cleanNotes,
      trashNotes: cleanTrash,
      ...(showTrash ? { trashCount: cleanTrash.length } : {})
    });
  },
  setTagFilter(tag) {
    set({ tagFilter: tag });
  },
  setShowSettings(open) {
    // backward-compat — setView 로 위임. mirror state (view, showTrash, showSettings) 동기 갱신.
    if (open) get().setView('settings');
    else get().setView('inbox');
  },
  setView(view) {
    set({
      view,
      showTrash: view === 'trash',
      showSettings: view === 'settings'
    });
    // settings/inbox 외 status view 면 해당 status fetch.
    if (view === 'completed' || view === 'archived' || view === 'trash') {
      void get().loadByView(view);
    }
    // v0.2.11 Cut D — review-* view 진입 시 aggregate 로드.
    if (view === 'review-daily') void get().loadReview('daily');
    if (view === 'review-weekly') void get().loadReview('weekly');
    if (view === 'review-monthly') void get().loadReview('monthly');
  },
  async loadByView(view) {
    const status = view === 'trash' ? 'trashed' : view;
    const notes = await inboxApi.listByStatus(status, { limit: 200 });
    if (view === 'trash') {
      set({ trashNotes: notes, trashCount: notes.length });
    } else {
      set({ notes });
    }
  },
  async toggleShowTrash() {
    const next = !get().showTrash;
    set({ showTrash: next });
    if (next) await get().loadTrash();
  },
  async loadTrash() {
    const trashNotes = await inboxApi.listTrash({ limit: 200 });
    set({ trashNotes, trashCount: trashNotes.length });
  },
  async restoreNote(id) {
    await inboxApi.restoreNote(id);
    // 낙관적 갱신: main 은 trash/restore 시 pushNoteUpdated 를 보내지 않음
    // (현재 AiWorker.onUpdate 만 push). 자가 반영이 primary 메커니즘.
    // 전제: 호출 시점에 trashNotes 에 노트가 존재 (T14 trash view 한정 호출).
    const note = get().trashNotes.find((n) => n.id === id);
    if (note) {
      get().upsertNote({ ...note, deletedAt: null });
    }
  },
  async permanentDeleteNote(id) {
    const r = await inboxApi.permanentDeleteNote(id);
    if (r.confirmed) get().removeNote(id);
  },
  async emptyTrash() {
    const r = await inboxApi.emptyTrash();
    if (r.confirmed) {
      set({ trashNotes: [], trashCount: 0 });
    }
  },
  async trashExpiredBatch(ids: string[]) {
    const r = await inboxApi.trashExpiredBatch(ids);
    if (!r.confirmed) return;
    const idSet = new Set(ids);
    set({
      expiredCandidates: get().expiredCandidates.filter((n) => !idSet.has(n.id)),
      notes: get().notes.filter((n) => !idSet.has(n.id)),
      trashCount: get().trashCount + r.trashedCount
    });
  },
  snoozeExpired() {
    set({ expiredSnoozeUntilMs: nextKstMidnightMs(Date.now()) });
  },
  async recheckOllama() {
    const status = await inboxApi.ollamaRecheck();
    set({ ollamaStatus: status });
  },
  async retryAllFailed() {
    await inboxApi.retryAllFailed();
    // 낙관적 갱신: failedCount = 0. AiWorker 처리 진행 중에 PendingBanner 가 N건 노출.
    // refreshMeta 가 트리거되면 자연 동기 (worker.onUpdate → main → renderer).
    // 반환된 r.count 는 의도적으로 무시 — 단일 process 환경 (Electron) 이라 race 무관,
    // 모든 ai_status='failed' 가 retry 대상이므로 사용자 시점 카운트는 0 으로 reset 가 정확.
    set({ failedCount: 0 });
  },
  async loadRecallCandidate() {
    const recallCandidate = await inboxApi.listRecallCandidate();
    set({ recallCandidate });
  },
  async openRecall(id) {
    await inboxApi.markRecallOpened(id);
    const recallCandidate = await inboxApi.listRecallCandidate();
    set({ recallCandidate });
  },
  async dismissRecallNote(id) {
    await inboxApi.dismissRecall(id);
    const recallCandidate = await inboxApi.listRecallCandidate();
    // m2 fix — dismiss 후 새 candidate 가 들어와도 이전 snooze 가 적용되지 않도록 clear
    set({ recallCandidate, recallSnoozeUntilMs: null });
  },
  async snoozeRecall() {
    set({ recallSnoozeUntilMs: nextKstMidnightMs(Date.now()) });
    // m1 fix — candidate=null 인 race 케이스 (사용자가 banner 닫힌 직후 클릭) 시
    // snooze 는 적용하되 emit 만 skip. telemetry 누락 받아들임 (의도적).
    const candidate = get().recallCandidate;
    if (candidate) {
      inboxApi.emitRecallSnoozed(candidate.id);
    }
  },
  // v0.2.11 Cut D — FTS5 search + review aggregate actions.
  setSearchQuery(q) {
    set({ searchQuery: q });
    if (q.trim().length === 0) set({ searchResults: null });
  },
  async searchNotes(q) {
    if (q.trim().length === 0) {
      set({ searchResults: null });
      return;
    }
    const view = get().view;
    // 회고/설정 view 일 때는 status filter 무의미 → 그대로 전체 검색
    const status = view === 'completed' || view === 'archived' || view === 'trash'
      ? (view === 'trash' ? 'trashed' : view)
      : view === 'inbox' ? 'active' : undefined;
    const r = await inboxApi.search(q, status ? { status } : {});
    set({ searchResults: r });
  },
  clearSearch() {
    set({ searchQuery: '', searchResults: null });
  },
  async loadReview(period) {
    const data = await inboxApi.reviewAggregate(period);
    set({ reviewData: data });
  }
}));
