// 돈돼지 가계부 데스크톱(Electron) 메인 프로세스.
// 라이브 사이트(https://app.sblog.kr)를 직접 로드한다 → 프론트 변경이 자동 반영(재설치 불필요),
// 같은 출처라 CORS 우회도 불필요. 배포된 웹 게이트가 Electron 을 감지해 안내페이지 대신 전체 앱을 띄운다.
//
// 로딩 UX: 예전에는 시작 시 HTTP 캐시를 통째로 비워 매번 전체 자산을 재다운로드 → 흰 화면이 오래 노출됐다.
// 이제 index.html 만 캐시버스트(?_=t)로 항상 최신을 받고, 해시된 정적 자산(JS/CSS)은 캐시를 유지해
// 재시작이 빠르다. 로드되는 동안에는 스플래시(로딩) 창을 띄워 빈 화면을 가린다.
// 또한 단일 인스턴스 잠금으로 중복 실행을 막아(두 번째 실행 시 사이트를 다시 로드하며 빈 화면이 보이던 문제),
// 이미 실행 중이면 기존 창을 활성화한다.
const { app, BrowserWindow, shell, ipcMain, Menu } = require('electron')
const fs = require('fs')
const path = require('path')

// 기본 애플리케이션 메뉴(File/Edit/View/Window) 제거 — 메인 창뿐 아니라
// 구글 로그인 팝업 등 자식 창에도 메뉴바가 안 붙도록 전역으로 비활성화.
Menu.setApplicationMenu(null)

// 네이티브 대화상자(alert/confirm) 제목은 app.getName() 을 쓴다 → package.json name('sb_pt') 대신 표기.
app.setName('돈돼지 가계부')

const APP_URL = 'https://app.sblog.kr'

// 현재 메인 창 참조(중복 실행 시 활성화 대상).
let mainWindow = null

// 중복 실행 방지: 잠금을 못 얻으면 이미 실행 중 → 새 인스턴스를 즉시 종료한다.
// (두 번째 실행에서 라이브 사이트를 다시 로드하느라 빈 화면이 보이던 현상 해결)
if (!app.requestSingleInstanceLock()) {
  app.quit()
} else {
  // 두 번째 실행 시도가 들어오면 기존 창을 복원/활성화.
  app.on('second-instance', () => {
    if (mainWindow && !mainWindow.isDestroyed()) {
      if (mainWindow.isMinimized()) mainWindow.restore()
      if (!mainWindow.isVisible()) mainWindow.show()
      mainWindow.focus()
    }
  })
}

// 창 크기/위치를 userData 에 저장해 다음 실행 때 복원 (기본 해상도 고정 대신 사용자가 맞춘 크기 유지)
function stateFile() {
  return path.join(app.getPath('userData'), 'window-state.json')
}
function loadState() {
  try {
    return JSON.parse(fs.readFileSync(stateFile(), 'utf-8'))
  } catch {
    return null
  }
}
function saveState(win) {
  try {
    if (win.isMinimized()) return
    const b = win.getBounds()
    fs.writeFileSync(stateFile(), JSON.stringify({ ...b, maximized: win.isMaximized() }))
  } catch {
    /* 저장 실패 무시 */
  }
}

// 스플래시(로딩) 화면 — 라이브 사이트가 첫 렌더될 때까지 빈 흰 화면 대신 표시.
function splashHtml() {
  const html = `<!doctype html><html><head><meta charset="utf-8"><title>돈돼지 가계부</title>
<style>html,body{height:100%;margin:0;display:flex;align-items:center;justify-content:center;
background:#1a1a1a;color:#eee;font-family:system-ui,'Segoe UI',sans-serif;-webkit-user-select:none;user-select:none}
.box{text-align:center}
.pig{font-size:46px;line-height:1}
.name{margin-top:10px;font-size:17px;font-weight:700;letter-spacing:-0.01em}
.spin{margin:18px auto 0;width:26px;height:26px;border:3px solid rgba(255,255,255,.2);
border-top-color:#00bc7e;border-radius:50%;animation:r .8s linear infinite}
@keyframes r{to{transform:rotate(360deg)}}
.tip{margin-top:12px;font-size:12px;opacity:.5}</style></head>
<body><div class="box"><div class="pig">🐷</div><div class="name">돈돼지 가계부</div>
<div class="spin"></div><div class="tip">불러오는 중…</div></div></body></html>`
  return 'data:text/html;charset=utf-8,' + encodeURIComponent(html)
}

// 오프라인/로드 실패 시 안내 화면 (다시 시도 버튼)
function offlineHtml() {
  const html = `<!doctype html><html><head><meta charset="utf-8"><title>돈돼지 가계부</title>
<style>html,body{height:100%;margin:0;display:flex;align-items:center;justify-content:center;
background:#1a1a1a;color:#eee;font-family:system-ui,'Segoe UI',sans-serif}
.box{text-align:center}h2{margin:0 0 8px}p{opacity:.7;margin:0}
button{margin-top:18px;padding:10px 22px;border:0;border-radius:8px;background:#00bc7e;color:#fff;
font-size:15px;font-weight:600;cursor:pointer}</style></head>
<body><div class="box"><h2>연결할 수 없습니다</h2><p>인터넷 연결을 확인해 주세요.</p>
<button onclick="location.href='${APP_URL}'">다시 시도</button></div></body></html>`
  return 'data:text/html;charset=utf-8,' + encodeURIComponent(html)
}

function createWindow() {
  const s = loadState()
  const win = new BrowserWindow({
    width: s?.width || 1024,
    height: s?.height || 720,
    x: s?.x,
    y: s?.y,
    minWidth: 360,
    minHeight: 560,
    show: false, // 첫 렌더(ready-to-show) 전까지 숨김 → 빈 흰 화면 노출 방지(스플래시가 대신 보임)
    backgroundColor: '#1a1a1a',
    title: '돈돼지 가계부',
    webPreferences: {
      contextIsolation: true,
      nodeIntegration: false,
      preload: path.join(__dirname, 'preload.cjs'),
      // 한글 IME 첫 글자 중복 입력 방지 — Chromium 맞춤법 검사기가 조합을 방해하는 Electron 알려진 버그 회피
      spellcheck: false,
    },
  })
  if (s?.maximized) win.maximize()
  mainWindow = win

  // 로딩 스플래시(프레임 없는 작은 창) — 메인이 준비되면 닫는다.
  const splash = new BrowserWindow({
    width: 300,
    height: 300,
    frame: false,
    resizable: false,
    movable: false,
    skipTaskbar: true,
    alwaysOnTop: true,
    backgroundColor: '#1a1a1a',
    title: '돈돼지 가계부',
  })
  splash.loadURL(splashHtml())
  splash.center()

  // 메인 창을 한 번만 노출하고 스플래시를 정리(이벤트 중복/지연 대비).
  let revealed = false
  const reveal = () => {
    if (revealed) return
    revealed = true
    if (!win.isDestroyed()) win.show()
    if (!splash.isDestroyed()) splash.destroy()
  }
  win.once('ready-to-show', reveal)
  // 안전장치: 로드가 비정상적으로 지연돼도 12초 후엔 메인 창을 띄운다.
  setTimeout(reveal, 12000)

  // 기본 메뉴바 제거(앱 자체 내비게이션 사용)
  win.removeMenu()
  // index.html 만 캐시버스트(?_=t) → 항상 최신 프론트. 해시 자산은 캐시 유지 → 빠른 재시작.
  win.loadURL(APP_URL + '/?_=' + Date.now())

  // 닫을 때 창 크기/위치 저장 → 다음 실행에 복원
  win.on('close', () => saveState(win))

  // 메인 프레임 로드 실패(오프라인 등) 시 안내 화면. -3(aborted, 리다이렉트 등)은 무시.
  win.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => {
    if (isMainFrame && errorCode !== -3) {
      win.loadURL(offlineHtml())
      reveal() // 오프라인 안내도 보이도록 메인 창 노출
    }
  })

  // 외부(앱 외 도메인) 링크는 기본 브라우저로, 앱 내 링크는 창에서 처리
  win.webContents.setWindowOpenHandler(({ url }) => {
    // 구글 로그인(GIS) 팝업은 앱 내부 자식 창으로 열어야 opener 로 로그인 결과(postMessage)가 돌아온다.
    // 외부 브라우저로 보내면 계정 선택 후 credential 이 앱으로 전달되지 않아 로그인이 끊긴다.
    if (/^https:\/\/accounts\.google\.com\//i.test(url)) {
      return { action: 'allow' }
    }
    if (/^https?:\/\//i.test(url) && !url.startsWith(APP_URL)) {
      shell.openExternal(url)
      return { action: 'deny' }
    }
    return { action: 'allow' }
  })
}

// 데스크톱 창 크기(해상도) 변경 — 렌더러(설정 화면)에서 호출
ipcMain.handle('window:setSize', (e, w, h) => {
  const win = BrowserWindow.fromWebContents(e.sender)
  if (!win) return false
  if (win.isMaximized()) win.unmaximize()
  win.setSize(Math.max(360, Math.round(w)), Math.max(560, Math.round(h)))
  win.center()
  return true
})
ipcMain.handle('window:getSize', (e) => {
  const win = BrowserWindow.fromWebContents(e.sender)
  return win ? win.getSize() : [0, 0]
})

app.whenReady().then(() => {
  // 캐시는 비우지 않는다(빠른 재시작). 최신 프론트는 index.html 캐시버스트로 보장.
  createWindow()
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})
