import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { singletonPanel } from './shared';

const store: { panel: vscode.WebviewPanel | null } = { panel: null };

export function openBase64Scanner(_context: vscode.ExtensionContext) {
  const { panel, isNew } = singletonPanel(store, 'hplToolbox.base64Scanner', 'Base64 Asset Scanner');
  if (!isNew) return;

  panel.webview.html = getHtml();
  panel.webview.onDidReceiveMessage(async (msg) => {
    if (msg.type === 'ready') {
      const ws = vscode.workspace.workspaceFolders?.[0];
      if (ws) {
        const distPath = path.join(ws.uri.fsPath, 'dist');
        if (fs.existsSync(distPath) && fs.statSync(distPath).isDirectory()) {
          panel.webview.postMessage({ type: 'folderPicked', path: distPath });
        }
      }
    } else if (msg.type === 'pickFolder') {
      const picked = await vscode.window.showOpenDialog({
        canSelectFolders: true,
        canSelectFiles: false,
        canSelectMany: false,
        openLabel: 'Select Folder',
      });
      if (picked && picked[0]) {
        panel.webview.postMessage({ type: 'folderPicked', path: picked[0].fsPath });
      }
    } else if (msg.type === 'pickFiles') {
      const picked = await vscode.window.showOpenDialog({
        canSelectFolders: false,
        canSelectFiles: true,
        canSelectMany: true,
        openLabel: 'Select Files',
        filters: { 'HTML': ['html', 'htm'] },
      });
      if (picked && picked.length) {
        panel.webview.postMessage({ type: 'filesPicked', paths: picked.map(u => u.fsPath) });
      }
    } else if (msg.type === 'scan') {
      const folder: string | undefined = msg.folder;
      const files: string[] | undefined = msg.files;
      let targets: string[] = [];
      let baseDir = '';
      if (files && files.length) {
        targets = files.filter(f => fs.existsSync(f));
        baseDir = targets.length ? commonBaseDir(targets) : '';
      } else if (folder && fs.existsSync(folder)) {
        targets = await collectHtmlFiles(folder);
        baseDir = folder;
      } else {
        panel.webview.postMessage({ type: 'results', results: [], error: 'Pick a folder or files first' });
        return;
      }
      const results: { file: string; ok: boolean; assets: string[] }[] = [];
      for (const file of targets) {
        try {
          const content = fs.readFileSync(file, 'utf8');
          const offenders = findNonBase64Assets(content);
          const display = baseDir ? path.relative(baseDir, file) || path.basename(file) : file;
          results.push({ file: display, ok: offenders.length === 0, assets: offenders });
        } catch {
          // skip unreadable
        }
      }
      panel.webview.postMessage({ type: 'results', results });
    }
  });
}

function commonBaseDir(files: string[]): string {
  if (files.length === 0) return '';
  if (files.length === 1) return path.dirname(files[0]);
  const split = files.map(f => path.dirname(f).split(path.sep));
  const first = split[0];
  let i = 0;
  while (i < first.length && split.every(parts => parts[i] === first[i])) i++;
  return first.slice(0, i).join(path.sep);
}

async function collectHtmlFiles(root: string): Promise<string[]> {
  const out: string[] = [];
  const stack: string[] = [root];
  while (stack.length) {
    const dir = stack.pop()!;
    let entries: fs.Dirent[];
    try {
      entries = fs.readdirSync(dir, { withFileTypes: true });
    } catch {
      continue;
    }
    for (const e of entries) {
      const full = path.join(dir, e.name);
      if (e.isDirectory()) {
        if (e.name === 'node_modules' || e.name === '.git') continue;
        stack.push(full);
      } else if (e.isFile() && /\.html?$/i.test(e.name)) {
        out.push(full);
      }
    }
  }
  return out;
}

function findNonBase64Assets(html: string): string[] {
  const markup = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
  const offenders = new Set<string>();
  const attrRegex = /\b(?:src|href|poster|data-src)\s*=\s*("([^"]*)"|'([^']*)')/gi;
  let m: RegExpExecArray | null;
  while ((m = attrRegex.exec(markup)) !== null) {
    const url = (m[2] ?? m[3] ?? '').trim();
    if (!url) continue;
    if (isAssetReference(url) && !isBase64DataUri(url)) {
      offenders.add(url);
    }
  }
  const cssUrlRegex = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)]*))\s*\)/gi;
  while ((m = cssUrlRegex.exec(markup)) !== null) {
    const url = (m[1] ?? m[2] ?? m[3] ?? '').trim();
    if (!url) continue;
    if (!isBase64DataUri(url)) {
      offenders.add(url);
    }
  }
  return [...offenders];
}

function isBase64DataUri(url: string): boolean {
  return /^data:[^;,]*;base64,/i.test(url);
}

function isAssetReference(url: string): boolean {
  if (url.startsWith('#') || url.toLowerCase().startsWith('javascript:')) return false;
  if (/^(mailto:|tel:)/i.test(url)) return false;
  return true;
}

function getHtml(): string {
  return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>
  body { font-family: var(--vscode-font-family); padding: 12px; color: var(--vscode-foreground); }
  .row { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
  input[type=text] { flex: 1; padding: 4px 6px; background: var(--vscode-input-background); color: var(--vscode-input-foreground); border: 1px solid var(--vscode-input-border); }
  button { padding: 4px 12px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; cursor: pointer; }
  button:hover { background: var(--vscode-button-hoverBackground); }
  .row-result { display: flex; gap: 8px; padding: 2px 0; font-family: var(--vscode-editor-font-family); font-size: 12px; }
  .mark { width: 14px; flex-shrink: 0; font-weight: bold; }
  .ok { color: #3fb950; }
  .bad { color: #f85149; }
  .file-name { word-break: break-all; }
  .assets { color: var(--vscode-descriptionForeground); margin-left: 6px; word-break: break-all; }
  .summary { margin-top: 12px; opacity: 0.8; }
</style>
</head>
<body>
  <h2>Base64 Asset Scanner</h2>
  <div class="row">
    <input id="folder" type="text" placeholder="Folder to scan recursively..." />
    <button id="pick">Browse Folder...</button>
    <button id="pickFiles">Pick Files...</button>
    <button id="scan">Scan</button>
  </div>
  <div id="selection" class="summary"></div>
  <div id="status"></div>
  <div id="results"></div>
<script>
  const vscode = acquireVsCodeApi();
  vscode.postMessage({ type: 'ready' });
  const folderEl = document.getElementById('folder');
  const statusEl = document.getElementById('status');
  const resultsEl = document.getElementById('results');
  const selectionEl = document.getElementById('selection');
  let pickedFiles = [];

  function clearFiles() {
    pickedFiles = [];
    selectionEl.textContent = '';
  }

  folderEl.addEventListener('input', clearFiles);

  document.getElementById('pick').addEventListener('click', () => {
    vscode.postMessage({ type: 'pickFolder' });
  });
  document.getElementById('pickFiles').addEventListener('click', () => {
    vscode.postMessage({ type: 'pickFiles' });
  });
  document.getElementById('scan').addEventListener('click', () => {
    statusEl.textContent = 'Scanning...';
    resultsEl.innerHTML = '';
    if (pickedFiles.length) {
      vscode.postMessage({ type: 'scan', files: pickedFiles });
    } else {
      const folder = folderEl.value.trim();
      if (!folder) { statusEl.textContent = 'Pick a folder or files first.'; return; }
      vscode.postMessage({ type: 'scan', folder });
    }
  });

  window.addEventListener('message', (event) => {
    const msg = event.data;
    if (msg.type === 'folderPicked') {
      folderEl.value = msg.path;
      clearFiles();
    } else if (msg.type === 'filesPicked') {
      pickedFiles = msg.paths;
      folderEl.value = '';
      selectionEl.textContent = pickedFiles.length + ' file(s) selected';
    } else if (msg.type === 'results') {
      if (msg.error) { statusEl.textContent = 'Error: ' + msg.error; return; }
      const total = msg.results.length;
      const flagged = msg.results.filter(r => !r.ok).length;
      statusEl.innerHTML = '<div class="summary">Scanned ' + total + ' HTML file(s). ' + flagged + ' contain non-base64 assets.</div>';
      resultsEl.innerHTML = '';
      for (const r of msg.results) {
        const row = document.createElement('div');
        row.className = 'row-result';
        const mark = document.createElement('span');
        mark.className = 'mark ' + (r.ok ? 'ok' : 'bad');
        mark.textContent = r.ok ? '✓' : '✗';
        const name = document.createElement('span');
        name.className = 'file-name';
        name.textContent = r.file;
        row.appendChild(mark);
        row.appendChild(name);
        if (!r.ok) {
          const a = document.createElement('span');
          a.className = 'assets';
          a.textContent = '— ' + r.assets.join(', ');
          row.appendChild(a);
        }
        resultsEl.appendChild(row);
      }
    }
  });
</script>
</body>
</html>`;
}
