// GameStream host - Electron main process (runs on your PC).
// Serves the capture page over http://localhost and keeps stream capture locked
// to the active PocketVR library item.

const { app, BrowserWindow, desktopCapturer, ipcMain, session, shell } = require('electron');
const { spawn } = require('child_process');
const http = require('http');
const fs = require('fs');
const path = require('path');
const { getArtFilePath, getSteamLibraryState } = require('./steam-library');

const PORT = 8088;
const HOST_DIR = __dirname;
const INTERNAL_TWITCH_TITLE = 'PocketVR Twitch';
const ICON_PATH = path.join(__dirname, 'assets', 'icon.ico');

let mainWindow = null;
let activeCaptureTarget = null;
let twitchWindow = null;

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function normalizeCaptureTarget(rawTarget) {
  if (!rawTarget || typeof rawTarget !== 'object') return null;
  const sourceType = rawTarget.sourceType === 'screen' ? 'screen' : 'window';
  const titlePattern = typeof rawTarget.titlePattern === 'string' ? rawTarget.titlePattern.trim() : '';
  const waitForWindowMs = Number(rawTarget.waitForWindowMs) > 0 ? Number(rawTarget.waitForWindowMs) : 12000;
  return {
    sourceType,
    titlePattern,
    waitForWindowMs,
  };
}

function sourceMatchesTarget(source, target) {
  if (!target) return false;
  if (target.sourceType === 'screen') return true;
  if (!target.titlePattern) return false;

  try {
    return new RegExp(target.titlePattern, 'i').test(source.name || '');
  } catch (error) {
    return String(source.name || '').toLowerCase().includes(String(target.titlePattern).toLowerCase());
  }
}

async function resolveCaptureSource(target) {
  if (!target) return null;

  if (target.sourceType === 'screen') {
    const sources = await desktopCapturer.getSources({
      types: ['screen'],
      thumbnailSize: { width: 0, height: 0 },
    });
    return sources[0] || null;
  }

  const deadline = Date.now() + target.waitForWindowMs;
  do {
    const sources = await desktopCapturer.getSources({
      types: ['window'],
      thumbnailSize: { width: 0, height: 0 },
      fetchWindowIcons: false,
    });
    const match = sources.find((source) => sourceMatchesTarget(source, target));
    if (match) return match;
    await delay(650);
  } while (Date.now() < deadline);

  return null;
}

function ensureTwitchWindow(url) {
  if (twitchWindow && !twitchWindow.isDestroyed()) {
    twitchWindow.show();
    twitchWindow.focus();
    if (url) twitchWindow.loadURL(url);
    return twitchWindow;
  }

  twitchWindow = new BrowserWindow({
    width: 1280,
    height: 800,
    title: INTERNAL_TWITCH_TITLE,
    backgroundColor: '#0b0e13',
    autoHideMenuBar: true,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      sandbox: true,
    },
  });

  twitchWindow.on('page-title-updated', (event) => {
    event.preventDefault();
    if (twitchWindow && !twitchWindow.isDestroyed()) {
      twitchWindow.setTitle(INTERNAL_TWITCH_TITLE);
    }
  });

  twitchWindow.on('closed', () => {
    twitchWindow = null;
    if (activeCaptureTarget && activeCaptureTarget.titlePattern === INTERNAL_TWITCH_TITLE) {
      activeCaptureTarget = null;
    }
  });

  twitchWindow.loadURL(url || 'https://www.twitch.tv/directory/game/Virtual%20Reality');
  return twitchWindow;
}

async function launchLibraryItem(payload) {
  if (!payload || typeof payload !== 'object') {
    throw new Error('Missing library item data.');
  }

  const launch = payload.launch || {};
  const capture = normalizeCaptureTarget(payload.capture);
  const title = typeof payload.title === 'string' ? payload.title : 'PocketVR item';

  if (!launch.type) {
    throw new Error('This library item does not have a launch method.');
  }

  if (launch.type === 'internal-twitch') {
    const url = typeof launch.url === 'string' && launch.url.trim()
      ? launch.url.trim()
      : 'https://www.twitch.tv/directory/game/Virtual%20Reality';
    ensureTwitchWindow(url);
    activeCaptureTarget = capture || {
      sourceType: 'window',
      titlePattern: INTERNAL_TWITCH_TITLE,
      waitForWindowMs: 5000,
    };
    return { ok: true, launched: true, message: `${title} opened inside PocketVR.` };
  }

  if (launch.type === 'url') {
    if (!launch.url || typeof launch.url !== 'string') {
      throw new Error('This library item is missing its launch URL.');
    }
    await shell.openExternal(launch.url);
    activeCaptureTarget = capture;
    return { ok: true, launched: true, message: `${title} launch requested.` };
  }

  if (launch.type === 'command') {
    if (!launch.command || typeof launch.command !== 'string') {
      throw new Error('This library item is missing its launch command.');
    }

    const args = Array.isArray(launch.args) ? launch.args.map(String) : [];
    const cwd = typeof launch.cwd === 'string' && launch.cwd.trim() ? launch.cwd : undefined;
    const child = spawn(launch.command, args, {
      cwd,
      detached: false,
      stdio: 'ignore',
      shell: false,
      windowsHide: false,
    });
    child.unref();
    activeCaptureTarget = capture;
    return { ok: true, launched: true, message: `${title} launch requested.` };
  }

  throw new Error(`Unsupported launch type: ${launch.type}`);
}

// Tiny local-only static server so the capture page loads from
// http://localhost (a secure context, required for getDisplayMedia).
function startLocalServer(onReady) {
  const MIME = {
    '.html': 'text/html',
    '.js': 'application/javascript',
    '.json': 'application/json',
    '.css': 'text/css',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.avif': 'image/avif',
    '.webp': 'image/webp',
    '.svg': 'image/svg+xml',
    '.ico': 'image/x-icon',
    '.ttf': 'font/ttf',
    '.woff': 'font/woff',
    '.woff2': 'font/woff2',
    '.txt': 'text/plain',
  };

  function sendFile(res, filePath) {
    fs.readFile(filePath, (err, data) => {
      if (err) {
        res.writeHead(404);
        res.end('Not found');
        return;
      }

      res.writeHead(200, {
        'Content-Type': MIME[path.extname(filePath).toLowerCase()] || 'application/octet-stream',
      });
      res.end(data);
    });
  }

  const server = http.createServer((req, res) => {
    const rawPath = decodeURIComponent((req.url || '').split('?')[0] || '/');

    if (rawPath.startsWith('/steam-art/')) {
      const token = rawPath.slice('/steam-art/'.length);
      const artPath = getArtFilePath(token);
      if (!artPath || !fs.existsSync(artPath)) {
        res.writeHead(404);
        res.end('Not found');
        return;
      }

      sendFile(res, artPath);
      return;
    }

    let rel = rawPath;
    if (rel === '/') rel = '/login.html';
    const filePath = path.normalize(path.join(HOST_DIR, rel));
    if (!filePath.startsWith(HOST_DIR)) {
      res.writeHead(403);
      res.end();
      return;
    }

    sendFile(res, filePath);
  });

  server.listen(PORT, '127.0.0.1', onReady);
}

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1320,
    height: 920,
    minWidth: 1160,
    minHeight: 780,
    title: 'PocketVR Host',
    backgroundColor: '#091009',
    icon: fs.existsSync(ICON_PATH) ? ICON_PATH : undefined,
    show: false,
    frame: false,
    autoHideMenuBar: true,
    fullscreen: true,
    fullscreenable: true,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js'),
    },
  });

  mainWindow.once('ready-to-show', () => {
    if (!mainWindow) return;
    mainWindow.setMenuBarVisibility(false);
    mainWindow.show();
    mainWindow.focus();
    mainWindow.maximize();
    mainWindow.setFullScreen(true);
  });

  mainWindow.on('closed', () => {
    mainWindow = null;
  });

  mainWindow.loadURL(`http://localhost:${PORT}/login.html`);
}

app.whenReady().then(() => {
  ipcMain.handle('library:launch', async (_event, payload) => {
    try {
      return await launchLibraryItem(payload);
    } catch (error) {
      return { ok: false, error: error.message };
    }
  });

  ipcMain.handle('library:clear-active', async () => {
    activeCaptureTarget = null;
    return { ok: true };
  });

  ipcMain.handle('steam:get-state', async () => getSteamLibraryState(false));
  ipcMain.handle('steam:rescan', async () => getSteamLibraryState(true));
  ipcMain.handle('window:toggle-fullscreen', async () => {
    if (!mainWindow || mainWindow.isDestroyed()) return { ok: false };
    const nextState = !mainWindow.isFullScreen();
    mainWindow.setFullScreen(nextState);
    if (nextState) {
      mainWindow.maximize();
      mainWindow.focus();
    }
    return { ok: true, fullScreen: nextState };
  });
  ipcMain.handle('window:set-fullscreen', async (_event, enabled) => {
    if (!mainWindow || mainWindow.isDestroyed()) return { ok: false };
    const fullScreen = !!enabled;
    mainWindow.setFullScreen(fullScreen);
    if (fullScreen) {
      mainWindow.maximize();
      mainWindow.focus();
    }
    return { ok: true, fullScreen };
  });
  ipcMain.handle('window:minimize', async () => {
    if (!mainWindow || mainWindow.isDestroyed()) return { ok: false };
    mainWindow.minimize();
    return { ok: true };
  });
  ipcMain.handle('window:close', async () => {
    if (!mainWindow || mainWindow.isDestroyed()) return { ok: false };
    mainWindow.close();
    return { ok: true };
  });

  session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
    resolveCaptureSource(activeCaptureTarget)
      .then((source) => callback(source ? { video: source } : {}))
      .catch(() => callback({}));
  });

  startLocalServer(createWindow);
});

app.on('window-all-closed', () => app.quit());
