import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { File } from 'node:buffer';
import { getToolWebviewStyles, handleClipboardAndOpen, singletonPanel } from './shared';

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

export function openPlecUpload(_context: vscode.ExtensionContext) {
  const { panel, isNew } = singletonPanel(store, 'hplToolbox.plecUpload', 'PLEC Upload');
  if (!isNew) return;

  panel.webview.html = getHtml();
  panel.webview.onDidReceiveMessage(async (msg) => {
    try {
      if (handleClipboardAndOpen(msg)) return;

      if (msg.type === 'pickFolder') {
        const picked = await vscode.window.showOpenDialog({
          canSelectFolders: true,
          canSelectFiles: false,
          canSelectMany: false,
          openLabel: 'Select Folder',
          defaultUri: getPickerDefaultUri(),
        });
        if (picked && picked[0]) {
          const files = await collectHtmlFiles(picked[0].fsPath);
          panel.webview.postMessage({
            type: 'filesSelected',
            files: files.map(filePath => ({ path: filePath, name: path.basename(filePath) })),
          });
        }
        return;
      }

      if (msg.type === 'pickFile') {
        const picked = await vscode.window.showOpenDialog({
          canSelectMany: true,
          filters: { HTML: ['html', 'htm'] },
          openLabel: 'Select',
          defaultUri: getPickerDefaultUri(),
        });
        if (picked && picked.length) {
          panel.webview.postMessage({
            type: 'filesSelected',
            rowId: msg.rowId,
            files: picked.map(u => ({ path: u.fsPath, name: path.basename(u.fsPath) })),
          });
        }
        return;
      }

      if (msg.type === 'upload') {
        await handleUpload(panel, msg.rows);
        return;
      }
    } catch (err: any) {
      panel.webview.postMessage({ type: 'error', message: err?.message || String(err) });
    }
  });
}

interface UploadRow {
  id: string;
  path: string;
  iteration: string;
}

async function handleUpload(panel: vscode.WebviewPanel, rows: UploadRow[]) {
  const cfg = vscode.workspace.getConfiguration('hplToolbox.plec');
  const uploadUrl = cfg.get<string>('uploadUrl')!;
  const originUrl = cfg.get<string>('originUrl')!;

  const valid: UploadRow[] = [];
  for (const r of rows) {
    if (!r.path || !fs.existsSync(r.path)) {
      panel.webview.postMessage({ type: 'rowError', rowId: r.id, message: 'File missing' });
      return;
    }
    if (!r.iteration || !r.iteration.trim()) {
      panel.webview.postMessage({ type: 'rowError', rowId: r.id, message: 'Iteration name required' });
      return;
    }
    if (!/\.html?$/i.test(r.path)) {
      panel.webview.postMessage({ type: 'rowError', rowId: r.id, message: 'Must be .html' });
      return;
    }
    valid.push(r);
  }
  if (valid.length === 0) {
    panel.webview.postMessage({ type: 'error', message: 'Nothing to upload.' });
    return;
  }

  const stamp = timestampSuffix();
  const stampedNames = valid.map((r) => appendTimestamp(path.basename(r.path), stamp));

  panel.webview.postMessage({ type: 'status', message: 'Uploading...' });

  const form = new FormData();
  for (let i = 0; i < valid.length; i++) {
    const r = valid[i];
    const idx = i + 1;
    panel.webview.postMessage({ type: 'status', message: `Preparing ${idx}/${valid.length} ${path.basename(r.path)}` });
    const buf = fs.readFileSync(r.path);
    const file = new File([buf], stampedNames[i], { type: 'text/html' });
    form.append(`fileUpload_${idx}`, file as any);
    form.append(`iterationNameUpload_${idx}`, encodeURI(r.iteration.replace(/\s+/g, '')));
  }
  panel.webview.postMessage({ type: 'status', message: `Uploading ${valid.length} file${valid.length === 1 ? '' : 's'}...` });

  let res: Response;
  try {
    res = await fetch(uploadUrl, {
      method: 'POST',
      body: form,
      headers: {
        'Origin': originUrl,
        'Referer': originUrl + '/',
        'User-Agent': 'Mozilla/5.0 (HPL-Toolbox-VSCode)',
        'X-Requested-With': 'XMLHttpRequest',
        'Accept': '*/*',
      },
    });
  } catch (err: any) {
    panel.webview.postMessage({ type: 'error', message: 'Network error: ' + (err?.message || err) });
    return;
  }

  const text = await res.text();
  if (!res.ok) {
    panel.webview.postMessage({
      type: 'error',
      message: `HTTP ${res.status}\n${text.slice(0, 800)}`,
    });
    return;
  }

  let json: any;
  try { json = JSON.parse(text); }
  catch {
    panel.webview.postMessage({
      type: 'error',
      message: `Server returned non-JSON:\n${text.slice(0, 800)}`,
    });
    return;
  }

  const preview = json.preview || json.previewURL || null;
  panel.webview.postMessage({ type: 'result', preview, raw: json, rawText: text });
}

function timestampSuffix(d: Date = new Date()): string {
  const pad = (n: number, w = 2) => String(n).padStart(w, '0');
  return (
    d.getFullYear().toString() +
    pad(d.getMonth() + 1) +
    pad(d.getDate()) +
    pad(d.getHours()) +
    pad(d.getMinutes()) +
    pad(d.getSeconds())
  );
}

function appendTimestamp(filename: string, stamp: string): string {
  const ext = path.extname(filename);
  const base = ext ? filename.slice(0, -ext.length) : filename;
  return `${base}_${stamp}${ext}`;
}

function getPickerDefaultUri(): vscode.Uri | undefined {
  const folders = vscode.workspace.workspaceFolders;
  if (!folders || folders.length === 0) return undefined;
  const root = folders[0].uri.fsPath;
  const dist = path.join(root, 'dist');
  if (fs.existsSync(dist) && fs.statSync(dist).isDirectory()) {
    return vscode.Uri.file(dist);
  }
  return vscode.Uri.file(root);
}

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 getHtml(): string {
  return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>
${getToolWebviewStyles()}
  #rows input[type=text] { width: 100%; }
  .file-cell { display: flex; align-items: center; gap: 8px; min-width: 0; }
  .row-error { color: var(--vscode-errorForeground); font-size: 12px; margin-top: 4px; }
  .pick { min-width: 82px; }
  .remove-btn {
    min-width: 28px;
    width: 28px;
    padding: 3px 0;
    color: var(--vscode-errorForeground);
    font-size: 16px;
    line-height: 1;
  }
  .result a { color: var(--vscode-textLink-foreground); }
  .result-actions { margin-top: 8px; display: flex; gap: 8px; }
  .pager { margin-top: 8px; display: flex; align-items: center; gap: 8px; justify-content: flex-end; }
</style>
</head>
<body>
<main class="tool-page">
<header class="tool-header">
  <h2 class="tool-title">PLEC Upload</h2>
  <p class="tool-description">Pick HTML files, give each an iteration name, then upload them to PLEC.</p>
</header>

<section class="tool-panel input-panel">
  <div class="panel-header">
    <h3 class="panel-title">Inputs</h3>
  </div>
  <div class="panel-body">
    <div class="control-group">
      <div class="file-row">
        <button id="pickFolder" class="secondary">Select Folder</button>
        <button id="pickFiles" class="secondary">Select File(s)</button>
        <button id="clear" class="secondary">Clear</button>
      </div>
      <div id="emptySelection" class="file-name" style="margin-top:8px;">(no files selected)</div>
      <div id="rowsPanel" class="results-panel is-hidden" style="margin-top:8px;">
        <table id="rows" class="data-table">
          <thead><tr><th>Filename</th><th style="width:240px;">Iteration Name</th><th style="width:44px;"></th></tr></thead>
          <tbody></tbody>
        </table>
      </div>
    </div>
    <div class="action-row">
      <button id="upload">Upload</button>
      <button id="openResult" class="secondary" disabled>Open</button>
      <button id="copyResult" class="secondary" disabled>Copy Link</button>
    </div>
    <div id="status" class="status-panel"></div>
  </div>
</section>
</main>

<script>
  const vscode = acquireVsCodeApi();
  const PAGE_SIZE = 5;
  let nextId = 1;
  let rows = [];
  let page = 0;
  let previewUrl = '';
  const tbody = document.querySelector('#rows tbody');
  const rowsPanel = document.getElementById('rowsPanel');
  const emptySelection = document.getElementById('emptySelection');

  function parseIterationName(filename) {
    const base = filename.replace(/\\.html?$/i, '');
    const tokens = base.split('_');
    for (const t of tokens) {
      if (/^(full|\\d+clk|\\d+s(?:ec)?)$/i.test(t)) return t.toLowerCase();
    }
    return base;
  }
  const statusEl = document.getElementById('status');
  const openResultBtn = document.getElementById('openResult');
  const copyResultBtn = document.getElementById('copyResult');

  function escapeHtml(s) {
    return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
  }

  function addFiles(files) {
    const existing = new Set(rows.map(r => r.path));
    files.forEach(f => {
      if (existing.has(f.path)) return;
      rows.push({
        id: 'r' + (nextId++),
        path: f.path,
        name: f.name,
        iteration: parseIterationName(f.name),
        error: ''
      });
      existing.add(f.path);
    });
    normalizeDuplicateIterations(files.length);
    page = Math.max(0, Math.ceil(rows.length / PAGE_SIZE) - 1);
    renderRows();
  }

  function normalizeDuplicateIterations(addedCount) {
    if (addedCount <= 1) return;
    const added = rows.slice(Math.max(0, rows.length - addedCount));
    if (!added.length) return;
    const first = added[0].iteration;
    if (first && added.every(r => r.iteration === first)) {
      added.forEach((r, i) => {
        r.iteration = String(i + 1).padStart(2, '0') + '_' + first;
      });
    }
  }

  function renderRows() {
    const oldPager = document.getElementById('selectedPager');
    if (oldPager) oldPager.remove();
    const maxPage = Math.max(0, Math.ceil(rows.length / PAGE_SIZE) - 1);
    page = Math.min(page, maxPage);
    rowsPanel.classList.toggle('is-hidden', rows.length === 0);
    emptySelection.classList.toggle('is-hidden', rows.length !== 0);
    if (!rows.length) {
      tbody.innerHTML = '';
      return;
    }
    const start = page * PAGE_SIZE;
    const pageRows = rows.slice(start, start + PAGE_SIZE);
    tbody.innerHTML = pageRows.map((row, offset) => {
      const i = start + offset;
      return \`
      <tr data-id="\${row.id}">
        <td>
          <span class="mono wrap">\${escapeHtml(row.name)}</span>
          <div class="row-error" data-error>\${escapeHtml(row.error || '')}</div>
        </td>
        <td><input type="text" data-iter data-index="\${i}" value="\${escapeHtml(row.iteration)}" /></td>
        <td><button class="remove-btn danger" data-remove="\${i}" title="Remove">×</button></td>
      </tr>
    \`;
    }).join('');
    tbody.querySelectorAll('[data-iter]').forEach(input => {
      input.addEventListener('input', () => {
        rows[Number(input.dataset.index)].iteration = input.value;
      });
    });
    tbody.querySelectorAll('[data-remove]').forEach(btn => {
      btn.addEventListener('click', () => {
        rows.splice(Number(btn.dataset.remove), 1);
        renderRows();
      });
    });
    if (rows.length > PAGE_SIZE) {
      const pager = document.createElement('div');
      pager.id = 'selectedPager';
      pager.className = 'pager';
      pager.innerHTML =
        '<button class="secondary" id="prevPage"' + (page === 0 ? ' disabled' : '') + '>Previous</button>' +
        '<span class="file-name">Page ' + (page + 1) + ' of ' + (maxPage + 1) + '</span>' +
        '<button class="secondary" id="nextPage"' + (page === maxPage ? ' disabled' : '') + '>Next</button>';
      document.querySelector('#rows').parentElement.after(pager);
      pager.querySelector('#prevPage').addEventListener('click', () => { page--; renderRows(); });
      pager.querySelector('#nextPage').addEventListener('click', () => { page++; renderRows(); });
    }
  }

  function setPreview(url) {
    previewUrl = url || '';
    openResultBtn.disabled = !previewUrl;
    copyResultBtn.disabled = !previewUrl;
  }

  document.getElementById('pickFolder').addEventListener('click', () => {
    vscode.postMessage({ type: 'pickFolder' });
  });
  document.getElementById('pickFiles').addEventListener('click', () => {
    vscode.postMessage({ type: 'pickFile' });
  });
  document.getElementById('clear').addEventListener('click', () => {
    rows = [];
    page = 0;
    setPreview('');
    renderRows();
    statusEl.textContent = '';
  });

  document.getElementById('upload').addEventListener('click', () => {
    clearErrors();
    statusEl.textContent = '';
    statusEl.classList.remove('is-busy');
    setPreview('');
    if (rows.length === 0) {
      statusEl.innerHTML = '<span class="err">Select at least one file.</span>';
      return;
    }
    statusEl.textContent = 'Uploading...';
    statusEl.classList.add('is-busy');
    vscode.postMessage({ type: 'upload', rows });
  });
  openResultBtn.addEventListener('click', () => {
    if (previewUrl) vscode.postMessage({ type: 'open', text: previewUrl });
  });
  copyResultBtn.addEventListener('click', () => {
    if (previewUrl) vscode.postMessage({ type: 'copy', text: previewUrl });
  });

  function clearErrors() {
    rows.forEach(row => row.error = '');
    renderRows();
  }

  window.addEventListener('message', (e) => {
    const m = e.data;
    if (m.type === 'filesSelected') {
      addFiles(m.files || []);
    } else if (m.type === 'status') {
      statusEl.textContent = m.message;
      statusEl.className = 'status-panel is-busy';
    } else if (m.type === 'error') {
      statusEl.classList.remove('is-busy');
      statusEl.innerHTML = '';
      const div = document.createElement('div');
      div.className = 'err';
      div.textContent = m.message;
      statusEl.appendChild(div);
    } else if (m.type === 'rowError') {
      statusEl.classList.remove('is-busy');
      const row = rows.find(r => r.id === m.rowId);
      if (row) row.error = m.message;
      renderRows();
      statusEl.innerHTML = '<span class="err">Fix row errors and retry.</span>';
    } else if (m.type === 'result') {
      statusEl.classList.remove('is-busy');
      statusEl.innerHTML = '<span class="ok">Upload complete.</span>';
      setPreview(m.preview);
    }
  });

  renderRows();
</script>
</body>
</html>`;
}
