import electron from 'electron';
const { app, Notification, dialog } = electron;
import '@shared/types';
import { existsSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { initLogger, logger } from './logger.js';
import { resolveProfilePaths } from './paths.js';
import { openDb } from './db/index.js';
import { NoteRepository } from './repository/NoteRepository.js';
import { MediaStore } from './services/MediaStore.js';
import { ContinuityService } from './services/ContinuityService.js';
import { CaptureService } from './services/CaptureService.js';
import { NotificationService } from './services/NotificationService.js';
import { HotkeyService } from './services/HotkeyService.js';
import { IntentService } from './services/IntentService.js';
import { HealthChecker } from './services/HealthChecker.js';
import { LocalOllamaProvider } from './ai/LocalOllamaProvider.js';
import { ProviderHolder } from './ai/ProviderHolder.js';
import { AiWorker } from './ai/AiWorker.js';
import { refreshVisionCache } from './services/VisionDetect.js';
import { registerCaptureApi } from './ipc/captureApi.js';
import { registerInboxApi, pushNoteUpdated, pushOllamaStatus } from './ipc/inboxApi.js';
import { registerSettingsApi, navigateInbox } from './ipc/settingsApi.js';
import { createInboxWindow, getInboxWindow } from './windows/inboxWindow.js';
import {
  createQuickCaptureWindow, showQuickCapture, getQuickCaptureWindow
} from './windows/quickCaptureWindow.js';
import { createTray, refreshTray } from './tray.js';
import { MediaGc } from './services/MediaGc.js';
import { BackupService } from './services/BackupService.js';
import { ExportService } from './services/ExportService.js';
import { ImportService } from './services/ImportService.js';
import { SyncService } from './services/SyncService.js';
import { SyncTimer } from './services/SyncTimer.js';
import { TelemetryService } from './services/TelemetryService.js';
import { SettingsService } from './services/SettingsService.js';
import { collectAutostartState } from './services/AutostartDiagnostic.js';
import { registerSchemesAsPrivileged, registerInklingMediaProtocol } from './protocol/inklingMedia.js';
import { DEFAULT_OLLAMA_ENDPOINT, DEFAULT_OLLAMA_MODEL } from '../shared/constants.js';

const HIDDEN_ARG = '--hidden';
const startedHidden = process.argv.includes(HIDDEN_ARG);

// v0.2.8 Cut A — `inkling-media://` custom protocol 스킴은 app.whenReady() 전에
// privileged 등록 필수 (Electron 표준). 이미지 asset 을 main process 가 직접
// 서빙해 file:// hack 없이 작동.
registerSchemesAsPrivileged();

// CRITICAL — single-instance lock + hidden-flag 전달 (v0.2.6 #46).
// 두 번째 .exe 가 hidden 으로 spawn 됐다면 (autostart) 첫 instance 의 inbox 창
// 띄우지 않음 — 사용자가 명시적으로 클릭한 게 아니므로.
const additionalData = { hidden: startedHidden };
const gotLock = app.requestSingleInstanceLock(additionalData);
if (!gotLock) {
  app.quit();
} else {
  app.on('second-instance', (_e, _argv, _cwd, secondData) => {
    const data = secondData as { hidden?: boolean } | undefined;
    // 두 번째가 hidden 으로 spawn (autostart 등) — UI 띄우지 않음
    if (data?.hidden === true) return;
    // 사용자가 명시적으로 .exe / 단축키 / 트레이로 띄움 → inbox 창 보이게
    const win = getInboxWindow();
    if (win) {
      if (win.isMinimized()) win.restore();
      if (!win.isVisible()) win.show();
      win.focus();
    } else {
      createInboxWindow();
    }
  });
}

app.whenReady().then(async () => {
  initLogger();
  logger.info('app.start', {
    platform: process.platform,
    version: app.getVersion(),
    packaged: app.isPackaged,
    hidden: startedHidden
  });

  const paths = resolveProfilePaths('default');

  // v0.2.8 Cut A — `inkling-media://` request handler 등록 (profileDir 결정 후).
  registerInklingMediaProtocol(paths.profileDir);

  const telemetry = new TelemetryService(join(paths.profileDir, 'telemetry'), () => new Date(), 14, { silent: true });
  void telemetry.cleanupOldFiles()
    .then((r) => logger.info('telemetry.cleanup', { removed: r.removed.length }))
    .catch((e) => logger.warn('telemetry.cleanup.failed', { reason: String(e) }));

  if (app.isPackaged && process.platform === 'win32') {
    const initFlag = join(paths.profileDir, '.autostart-init');
    if (!existsSync(initFlag)) {
      app.setLoginItemSettings({ openAtLogin: true, args: [HIDDEN_ARG] });
      writeFileSync(initFlag, new Date().toISOString());
      logger.info('autostart.enabled.firstRun');
    }
    // v0.2.6 #45 진단 — startup 로그. 같은 정보가 SettingsPage 진단 패널에도 surface (collectAutostartState single source of truth).
    void collectAutostartState().then((state) => logger.info('autostart.state', { ...state }));
  }
  const db = openDb(paths.dbFile);
  const repo = new NoteRepository(db);
  const store = new MediaStore(paths.profileDir);
  const continuity = new ContinuityService(db);
  const intent = new IntentService(repo);

  const settingsSvc = new SettingsService(paths.profileDir);
  const settings = await settingsSvc.load();

  const resolvedEndpoint = settings.ollama?.endpoint
                         ?? process.env.INKLING_OLLAMA_ENDPOINT
                         ?? DEFAULT_OLLAMA_ENDPOINT;
  const resolvedModel = settings.ollama?.model ?? DEFAULT_OLLAMA_MODEL;

  logger.info('ai.endpoint', {
    endpoint: resolvedEndpoint,
    model: resolvedModel,
    source: settings.ollama?.endpoint
      ? 'settings'
      : (process.env.INKLING_OLLAMA_ENDPOINT ? 'env' : 'default')
  });

  const provider = new LocalOllamaProvider({ endpoint: resolvedEndpoint, model: resolvedModel });
  const providerHolder = new ProviderHolder(provider);

  // v0.3.1 Cut F — app launch 시 vision capability cache 갱신 (fire-and-forget).
  // 실패 silent (cache 유지). 사용자가 설정 페이지에서 "다시 감지" manual trigger 가능.
  void refreshVisionCache({ settings: settingsSvc, endpoint: resolvedEndpoint }).catch(() => {});

  const health = new HealthChecker(providerHolder, {
    // v0.2.9 Cut B Task 14 — AI 비활성 시 health polling skip (Ollama 미설치 환경 무영향).
    isAiEnabled: () => settingsSvc.isAiEnabled(),
    onUpdate: (status) => {
      logger.info('ai.health', { ...status } as Record<string, unknown>);
      pushOllamaStatus(getInboxWindow, status);
    },
    onTelemetry: (ev) => {
      if (ev.kind === 'ollama_unreachable') {
        void telemetry.emit({ kind: 'ollama_unreachable', payload: { reason: ev.reason } }).catch(() => {});
      } else if (ev.kind === 'ollama_recovered') {
        void telemetry.emit({ kind: 'ollama_recovered', payload: { downtimeMs: ev.downtimeMs } }).catch(() => {});
      } else if (ev.kind === 'ollama_recheck_manual') {
        void telemetry.emit({ kind: 'ollama_recheck_manual', payload: {} }).catch(() => {});
      }
    }
  });
  health.start();

  const worker = new AiWorker(repo, providerHolder, {
    onUpdate: (note) => {
      pushNoteUpdated(getInboxWindow, note);
      // F4-C: AI 처리 완료 = 새 캡처가 inbox 에 합류한 시점, tray 도 즉시 갱신.
      // v0.2.7 Phase 3 — failedCount 메뉴 항목 제거됨 → todayCount 만 갱신.
      refreshTray({ todayCount: repo.countToday() });
    },
    logger,
    telemetry,
    // v0.3.1 Cut F — vision 지원
    settings: settingsSvc,
    mediaStore: store
  });

  const notify = new NotificationService({
    isSupported: () => Notification.isSupported(),
    send: (body) => {
      new Notification({ title: 'Inkling', body, silent: false }).show();
    }
  });

  const capture = new CaptureService(repo, store, {
    enqueue: (id) => worker.enqueue(id),
    celebrate: (id) => notify.celebrate(id),
    telemetry,
    settings: settingsSvc
  });

  registerCaptureApi(capture, getQuickCaptureWindow);
  registerInboxApi({
    repo, continuity, capture, health, intent,
    getInboxWindow, settings: settingsSvc, providerHolder,
    paths: { profileDir: paths.profileDir },
    // v0.2.9 Cut B Task 16 — disabled 메모 일괄 재투입 시 in-memory queue 갱신.
    enqueue: (id) => worker.enqueue(id)
  });
  // registerSettingsApi 는 backup / exportSvc / importSvc / syncSvc / telemetry 가
  // 생성된 뒤에 호출 (Task 10) — 아래 BackupService/ExportService/... 초기화 직후로 이동.

  const hotkeys = new HotkeyService();
  const reg = hotkeys.register({
    accelerator: process.platform === 'darwin' ? 'Cmd+Shift+J' : 'Ctrl+Shift+J',
    onTrigger: () => showQuickCapture()
  });
  if (!reg.ok) logger.warn('hotkey.register.failed', { reason: reg.reason });

  if (!startedHidden) {
    createInboxWindow();
  }
  createQuickCaptureWindow();
  await worker.loadFromDb();

  const gc = new MediaGc(db, store);
  void gc.run()
    .then((r) => logger.info('media.gc', { ...r } as Record<string, unknown>))
    .catch((e) => logger.warn('media.gc.failed', { reason: String(e) }));

  const exportSvc = new ExportService(repo, store);
  const importSvc = new ImportService(repo, store);
  const syncSvc = new SyncService(paths.profileDir, exportSvc, importSvc);
  const syncTimer = new SyncTimer(syncSvc, settingsSvc);

  const backup = new BackupService(db, join(paths.profileDir, 'backups'));
  void backup.runDaily()
    .then((r) => logger.info('backup.daily', { ...r } as Record<string, unknown>))
    .catch((e) => logger.warn('backup.daily.failed', { reason: String(e) }));

  // v0.2.7 Task 10 — 설정 페이지 IPC (autostart + backup/export/import/sync/telemetry).
  // backup / exportSvc / importSvc / syncSvc / telemetry 가 모두 준비된 뒤 등록.
  registerSettingsApi({
    backup, exportSvc, importSvc, syncSvc, telemetry, settings: settingsSvc, getInboxWindow,
    syncTimer
  });

  void syncTimer.start();

  let backupOnQuitDone = false;
  let trayInterval: NodeJS.Timeout | null = null;
  app.on('before-quit', (e) => {
    // 모든 cleanup 한 곳에 통합 — sync (idempotent) → async backup chain.
    health.stop();
    syncTimer.stop();
    if (trayInterval !== null) {
      clearInterval(trayInterval);
      trayInterval = null;
    }
    if (backupOnQuitDone) return;
    e.preventDefault();
    backup.runDaily()
      .then((r) => logger.info('backup.beforeQuit', { ...r } as Record<string, unknown>))
      .catch((e2) => logger.warn('backup.beforeQuit.failed', { reason: String(e2) }))
      .then(() => syncSvc.isConfigured().then((cfg) => {
        if (!cfg) return;
        return syncSvc.sync()
          .then((r) => logger.info('sync.beforeQuit', {
            ok: r.ok,
            changed: r.changed ?? false,
            pushed: r.pushed ?? false,
            reason: r.reason
          } as Record<string, unknown>))
          .catch((e3) => logger.warn('sync.beforeQuit.failed', { reason: String(e3) }));
      }))
      .finally(() => {
        backupOnQuitDone = true;
        app.isQuitting = true;
        app.quit();
      });
  });

  // v0.2.7 Phase 3 (Task 16) — TrayCallbacks 슬림: 10 → 3.
  // 백업/내보내기/복원/동기화/사용 로그/Ollama 재확인/AI 재처리/Ollama 설정/정보 →
  // 모두 설정 페이지로 이전 (registerSettingsApi 의 IPC 핸들러가 본문 보유).
  createTray({
    showInbox: () => createInboxWindow(),
    showCapture: () => showQuickCapture(),
    showSettings: () => navigateInbox('settings')
  });

  // F4-C 환경 앵커 — tray tooltip + 메뉴 첫 항목을 오늘 KST 캡처 수로 갱신.
  // 초기 1회 + 60s interval. AiWorker.onUpdate 도 별도 갱신 트리거.
  // cleanup 은 위 통합 before-quit 핸들러에서 처리.
  // v0.2.7 Phase 3 — failedCount 메뉴 항목 제거됨 → todayCount 만 갱신.
  refreshTray({ todayCount: repo.countToday() });
  trayInterval = setInterval(() => {
    refreshTray({ todayCount: repo.countToday() });
  }, 60_000);

  // F14 (v0.2.7) — macOS dock 클릭 시 hidden inbox 창 show/focus.
  // 기존: BrowserWindow.getAllWindows().length === 0 만 검사 → quickCapture 등이
  // 떠 있으면 inbox 창이 hidden 인 채로 남았음.
  app.on('activate', () => {
    const win = getInboxWindow();
    if (win && !win.isDestroyed()) {
      if (!win.isVisible()) win.show();
      win.focus();
    } else {
      createInboxWindow();
    }
  });
});
