import { app, BrowserWindow, screen } from 'electron'
import fs from 'fs'

import minimist from 'minimist'

const RETRY_DELAY = 3000 // ms

const getArgs = () => {
  const parsed = minimist(process.argv.slice(1))
  return {
    showHelp: parsed.help || parsed.h,
    url: parsed.url || 'http://[::1]:7781',
    windowSize: parsed.size ? parsed.size.split('x').map(Number) : undefined,
    quitFilePath: 'quit',
    stateFilePath: '/atx300/state/dispatch_gui.json'
  }
}

const args = getArgs()

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
let deinitFileLogging

let quitIntervalId
let retryTimeoutId

function showHelpText () {
  console.log(`
Usage: dispatch_gui [options]

Options:
  --help, -h            Show this help text
  --url <URL>           Specify the URL to load (default: http://[::1]:7781)
  --size <WIDTHxHEIGHT> Set the initial window size (e.g., --size 1600x900)
`)
  app.exit()
}

if (args.showHelp) {
  showHelpText()
} else {
  deinitFileLogging = initFileLogging()

  console.log('*'.repeat(40))
  console.log(`starting dispatch_gui v${app.getVersion()}`)
  console.log(`electron version: ${process.versions.electron}`)
  console.log(`node version: ${process.versions.node}`)
  console.log(`chrome version: ${process.versions.chrome}`)

  if (fs.existsSync(args.quitFilePath)) {
    console.log('stale quit file found, removing it')
    fs.unlinkSync(args.quitFilePath)
  }
}

function formatCurrentTime () {
  const now = new Date()

  const year = now.getFullYear()
  const month = String(now.getMonth() + 1).padStart(2, '0')
  const day = String(now.getDate()).padStart(2, '0')
  const hours = String(now.getHours()).padStart(2, '0')
  const minutes = String(now.getMinutes()).padStart(2, '0')
  const seconds = String(now.getSeconds()).padStart(2, '0')
  const milliseconds = String(now.getMilliseconds()).padStart(3, '0')

  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`
}

function initFileLogging () {
  let logStream = fs.createWriteStream('/atx300/log/dispatch_gui.log', { flags: 'a' })

  const fileConsole = consoleMethod => {
    return (message, ...args) => {
      logStream?.write(`${formatCurrentTime()}: ${consoleMethod.name.toUpperCase()}: ${message} ${args}\n`)
      consoleMethod(message, ...args)
    }
  }

  const consoleDebugOriginal = console.debug
  const consoleLogOriginal = console.log
  const consoleErrorOriginal = console.error
  console.debug = fileConsole(console.debug)
  console.log = fileConsole(console.log)
  console.error = fileConsole(console.error)

  return () => {
    console.debug('deinitFileLogging')
    console.debug = consoleDebugOriginal
    console.log = consoleLogOriginal
    console.error = consoleErrorOriginal
    logStream?.close()
    logStream = undefined
  }
}

const windowOptions = () => {
  const options = {
    webPreferences: {
      nodeIntegration: true
    },
    scrollBounce: false,
    autoHideMenuBar: true,
    resizable: true,
    frame: true,
    title: 'atxdispatch'
  }

  if (args.windowSize?.length > 0) {
    options.width = args.windowSize[0]
    options.height = args.windowSize[1]
  } else {
    const { width, height } = screen.getPrimaryDisplay().workAreaSize
    options.width = width * 0.8
    options.height = height * 0.8
  }

  return options
}

function loadWindowState () {
  try {
    if (fs.existsSync(args.stateFilePath)) {
      console.log(`Loading window state from ${args.stateFilePath}`)
      const stateData = fs.readFileSync(args.stateFilePath, 'utf8')
      const state = JSON.parse(stateData)

      // Check if state has window property
      if (state.window) {
        if (state.window.bounds) {
          console.log(`Loaded window bounds: x=${state.window.bounds.x}, y=${state.window.bounds.y}, width=${state.window.bounds.width}, height=${state.window.bounds.height}`)
        }

        if (state.window.isMaximized !== undefined) {
          console.log(`Loaded window maximized state: ${state.window.isMaximized}`)
        }

        if (state.window.isFullScreen !== undefined) {
          console.log(`Loaded window fullscreen state: ${state.window.isFullScreen}`)
        }

        if (state.window.zoomLevel !== undefined) {
          console.log(`Loaded window zoom level: ${state.window.zoomLevel}`)
        }

        return state.window
      } else {
        // For backward compatibility with old format
        if (state.bounds) {
          console.log('Loaded window bounds from legacy format')
          return state
        }
      }
    } else {
      console.log(`Window state file not found at ${args.stateFilePath}`)
    }
  } catch (error) {
    console.error('Failed to load window state:', error)
  }
  return null
}

function saveWindowState () {
  if (!mainWindow) {
    console.error('no window when trying to save its position')
    return
  }

  try {
    const bounds = mainWindow.getBounds()
    const isMaximized = mainWindow.isMaximized()
    const isFullScreen = mainWindow.isFullScreen()
    const zoomLevel = mainWindow.webContents.zoomLevel
    const screenResolution = screen.getPrimaryDisplay().workAreaSize

    const stateData = JSON.stringify({
      window: {
        bounds,
        isMaximized,
        isFullScreen,
        zoomLevel,
        screenResolution
      }
    }, null, 2)

    // Ensure directory exists
    const stateDir = args.stateFilePath.substring(0, args.stateFilePath.lastIndexOf('/'))
    if (!fs.existsSync(stateDir)) {
      fs.mkdirSync(stateDir, { recursive: true })
    }

    fs.writeFileSync(args.stateFilePath, stateData)
    console.log('Window state saved to', args.stateFilePath)
  } catch (error) {
    console.error('Failed to save window state:', error)
  }
}

function createWindow () {
  const options = windowOptions()
  const savedState = loadWindowState()

  const { height: screenHeight } = screen.getPrimaryDisplay().workAreaSize
  console.log(`Screen resolution: height=${screenHeight}px`)

  let initialZoom = 0
  let shouldUseSavedZoom = false
  
  if (savedState && savedState.zoomLevel !== undefined) {
    // Check if screen resolution changed
    const savedScreenHeight = savedState.screenResolution?.height
    
    if (savedScreenHeight === screenHeight) {
      initialZoom = savedState.zoomLevel
      console.log(`using saved zoom level: ${initialZoom}`)
      shouldUseSavedZoom = true
    } else {
      console.log(`screen resolution changed from ${savedScreenHeight}px to ${screenHeight}px, recalculating zoom`)
    }
  }
  
  if (!shouldUseSavedZoom) {
    if (screenHeight <= 768) { // legacy atx300 systems
      initialZoom = -2 // -2 is approximately 70%
      console.log(`low-res system detected, using zoom level: ${initialZoom}`)
    } else {
      console.log('using default zoom level (0)')
    }
  }

  if (savedState && savedState.bounds) {
    options.x = savedState.bounds.x
    options.y = savedState.bounds.y
    options.width = savedState.bounds.width
    options.height = savedState.bounds.height
    console.log(`Applied window bounds: x=${options.x}, y=${options.y}, width=${options.width}, height=${options.height}`)
  } else {
    console.log('Using default window size and position')
  }

  mainWindow = new BrowserWindow(options)

  if (initialZoom !== 0) {
    console.log(`Setting initial zoom level: ${initialZoom}`)
    mainWindow.webContents.zoomLevel = initialZoom
  }

  if (savedState) {
    if (savedState.isMaximized) {
      mainWindow.maximize()
    }
    if (savedState.isFullScreen) {
      mainWindow.setFullScreen(true)
    }
  }

  // Open the DevTools.
  // mainWindow.webContents.openDevTools()

  // Window can be closed normally with a single click

  mainWindow.on('close', function () {
    // Save window state before the window is closed
    saveWindowState()
  })

  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null
  })
  mainWindow.webContents.on('did-finish-load', () => {
    mainWindow.webContents.executeJavaScript('document.body.innerHTML').then((html) => {
      if (html.toLowerCase().includes('server error')) {
        console.error('Page loaded with server error. Retrying in 3 seconds...')
        // TODO: this is a very disturbing message - maybe just show some spinner or something...
        const errorMessage = `
<pre>
+====+
|(::)|
| )( |
|(..)|
+====+
</pre>
<p>Retrying in 3 seconds...</p>
`
        mainWindow?.webContents.executeJavaScript(`document.body.innerHTML = \`${errorMessage}\`;`).catch(console.error)
        clearTimeout(retryTimeoutId)
        retryTimeoutId = setTimeout(() => {
          mainWindow?.loadURL(args.url)
        }, RETRY_DELAY)
      } else {
        console.log('Page loaded successfully')
      }
    }).catch(console.error)
  })

  mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL, _isMainFrame) => {
    console.error(`Failed to load page: ${validatedURL}, with error: ${errorDescription} (Code: ${errorCode}). Retrying in 3 seconds...`)
    const errorMessage = `<h1>Failed to load</h1><p>Error: ${errorDescription} (Code: ${errorCode})</p><p>Retrying in 3 seconds...</p>`
    mainWindow?.webContents.executeJavaScript(`document.body.innerHTML = \`${errorMessage}\`;`).catch(console.error)
    clearTimeout(retryTimeoutId)
    retryTimeoutId = setTimeout(() => {
      mainWindow?.loadURL(args.url)
    }, RETRY_DELAY)
  })

  mainWindow.webContents.on('before-input-event', (event, input) => {
    if (input.control && input.shift && input.key.toLowerCase() === 'i') {
      mainWindow.webContents.openDevTools()
      event.preventDefault()
    } else if (input.control && input.key.toLowerCase() === 'r') {
      mainWindow.reload()
      event.preventDefault()
    } else if (input.control && input.key.toLowerCase() === 'f') {
      mainWindow.setFullScreen(!mainWindow.isFullScreen())
      event.preventDefault()
    } else if (input.control && (input.key === '=' || input.key === '+' || input.key === 'numadd')) {
      // Zoom in (Ctrl++) - support both regular and numpad +
      mainWindow.webContents.zoomLevel += 0.5
      const newZoomLevel = mainWindow.webContents.zoomLevel
      console.log(`Zoom level increased to: ${newZoomLevel} (${Math.round((Math.pow(1.2, newZoomLevel) * 100))}%)`)
      event.preventDefault()
    } else if (input.control && (input.key === '-' || input.key === 'numsub')) {
      // Zoom out (Ctrl+-) - support both regular and numpad -
      mainWindow.webContents.zoomLevel -= 0.5
      const newZoomLevel = mainWindow.webContents.zoomLevel
      console.log(`Zoom level decreased to: ${newZoomLevel} (${Math.round((Math.pow(1.2, newZoomLevel) * 100))}%)`)
      event.preventDefault()
    } else if (input.control && (input.key === '0' || input.key === 'num0')) {
      // Reset zoom (Ctrl+0) - support both regular and numpad 0
      mainWindow.webContents.zoomLevel = 0
      console.log('Zoom level reset to: 0 (100%)')
      event.preventDefault()
    }
  })

  quitIntervalId = setInterval(checkQuitFile, 1000)

  mainWindow.loadURL(args.url)
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)

app.on('will-quit', () => {
  // This should be the last event to fire before exit
  // https://github.com/electron/electron/blob/main/docs/api/app.md#event-will-quit
  console.debug('will-quit')
  deinitFileLogging?.()
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
  console.debug('window-all-closed')
  if (process.platform !== 'darwin') {
    console.debug('app.quit()')
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) {
    console.debug('createWindow()')
    createWindow()
  }
})

function cleanUp () {
  console.debug('setting window to null')
  if (mainWindow) {
    saveWindowState()
    mainWindow = null
  }
  console.debug('will clear intervals and timeouts')
  console.debug(`quitIntervalId: ${quitIntervalId}, retryTimeoutId: ${retryTimeoutId}`)
  clearInterval(quitIntervalId)
  clearTimeout(retryTimeoutId)
}

function checkQuitFile () {
  if (fs.existsSync(args.quitFilePath)) {
    console.log('quit file found, removing it and exiting')
    fs.unlinkSync(args.quitFilePath)
    cleanUp()
    console.debug('will quit app')
    app.quit()
  }
}
