// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
//
// SPDX-License-Identifier: GPL-2.0-or-later

import { hostCore } from "./host.js";

import type { PluginRuntimeWindow } from "./runtime-contract.js";

export {};

interface WefaxImage {
  ts_ms?: number;
  ioc?: number;
  lpm?: number;
  line_count?: number;
  path?: string;
  _tsMs?: number;
  _ts?: string;
  _dataUrl?: string;
}

interface WefaxProgress {
  state?: string;
  line_data?: string;
  line_count?: number;
  pixels_per_line?: number;
  ioc?: number;
  lpm?: number;
}

interface WefaxBridge {
  getDecodeHistoryRetentionMs?: () => number;
  trxScheduleUiFrameJob?: (key: string, job: () => void) => void;
  takeSchedulerControlForDecoderDisable?: (button: HTMLElement) => Promise<unknown>;
  syncWefaxToggle?: (enabled: boolean) => void;
}

const wefaxWindow = window as unknown as WefaxBridge & PluginRuntimeWindow;

// ---------------------------------------------------------------------------
// wefax.js — WEFAX decoder plugin for trx-frontend-http
// Live view: decoder state, live canvas, latest image card
// History view: filterable table of all decoded images
// ---------------------------------------------------------------------------

// ── DOM references (cached once) ───────────────────────────────────
const wefaxDom = {
  status:         document.getElementById('wefax-status'),
  liveView:       document.getElementById('wefax-live-view'),
  historyView:    document.getElementById('wefax-history-view'),
  liveContainer:  document.getElementById('wefax-live-container'),
  liveInfo:       document.getElementById('wefax-live-info'),
  liveCanvas:     document.getElementById('wefax-live-canvas') as HTMLCanvasElement | null,
  liveLatest:     document.getElementById('wefax-live-latest'),
  historyList:    document.getElementById('wefax-history-list'),
  historyCount:   document.getElementById('wefax-history-count'),
  filterInput:    document.getElementById('wefax-filter') as HTMLInputElement | null,
  sortSelect:     document.getElementById('wefax-sort') as HTMLSelectElement | null,
  toggleBtn:      document.getElementById('wefax-decode-toggle-btn'),
  clearBtn:       document.getElementById('wefax-clear-btn'),
  viewLiveBtn:    document.getElementById('wefax-view-live'),
  viewHistoryBtn: document.getElementById('wefax-view-history'),
};

// ── State ───────────────────────────────────────────────────────────
let wefaxImageHistory: WefaxImage[] = [];
const WEFAX_MAX_IMAGES = 100;
let wefaxLiveCtx: CanvasRenderingContext2D | null = null;
let wefaxLiveLineCount = 0;
let wefaxLivePixelsPerLine = 1809;
let wefaxActiveView: 'live' | 'history' = 'live';
let wefaxFilterText = '';

// ── Helpers ─────────────────────────────────────────────────────────
function currentWefaxHistoryRetentionMs(): number {
  return wefaxWindow.getDecodeHistoryRetentionMs?.() ?? 24 * 60 * 60 * 1000;
}

function pruneWefaxHistory() {
  const cutoff = Date.now() - currentWefaxHistoryRetentionMs();
  wefaxImageHistory = wefaxImageHistory.filter(function (m) { return (m._tsMs || 0) > cutoff; });
}

function escapeHtml(s: unknown): string {
  return String(s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

function scheduleWefaxUi(key: string, job: () => void): void {
  if (typeof wefaxWindow.trxScheduleUiFrameJob === 'function') {
    wefaxWindow.trxScheduleUiFrameJob(key, job);
    return;
  }
  job();
}

// ── View switching ──────────────────────────────────────────────────
function switchWefaxView(view: 'live' | 'history'): void {
  wefaxActiveView = view;
  if (wefaxDom.liveView)    wefaxDom.liveView.style.display = view === 'live' ? '' : 'none';
  if (wefaxDom.historyView) wefaxDom.historyView.style.display = view === 'history' ? '' : 'none';

  [wefaxDom.viewLiveBtn, wefaxDom.viewHistoryBtn].forEach(function (btn) {
    if (btn) btn.classList.remove('sat-view-active');
  });
  if (view === 'live' && wefaxDom.viewLiveBtn)       wefaxDom.viewLiveBtn.classList.add('sat-view-active');
  if (view === 'history' && wefaxDom.viewHistoryBtn)  wefaxDom.viewHistoryBtn.classList.add('sat-view-active');

  if (view === 'history') renderWefaxHistoryTable();
}

if (wefaxDom.viewLiveBtn)    wefaxDom.viewLiveBtn.addEventListener('click', function () { switchWefaxView('live'); });
if (wefaxDom.viewHistoryBtn) wefaxDom.viewHistoryBtn.addEventListener('click', function () { switchWefaxView('history'); });

// ── Live canvas rendering ───────────────────────────────────────────
function resetLiveCanvas(pixelsPerLine: number): void {
  const canvas = wefaxDom.liveCanvas;
  if (!canvas) return;
  wefaxLivePixelsPerLine = pixelsPerLine;
  wefaxLiveLineCount = 0;
  canvas.width = pixelsPerLine;
  canvas.height = 800;
  wefaxLiveCtx = canvas.getContext('2d');
  if (!wefaxLiveCtx) return;
  wefaxLiveCtx.fillStyle = '#000';
  wefaxLiveCtx.fillRect(0, 0, canvas.width, canvas.height);
  if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = '';
}

function paintLine(lineBytes: Uint8Array): void {
  const canvas = wefaxDom.liveCanvas;
  if (!wefaxLiveCtx || !canvas) return;
  const y = wefaxLiveLineCount;

  if (y >= canvas.height) {
    const old = wefaxLiveCtx.getImageData(0, 0, canvas.width, canvas.height);
    canvas.height *= 2;
    wefaxLiveCtx = canvas.getContext('2d');
    if (!wefaxLiveCtx) return;
    wefaxLiveCtx.putImageData(old, 0, 0);
  }

  const w = wefaxLivePixelsPerLine;
  const imgData = wefaxLiveCtx.createImageData(w, 1);
  const d = imgData.data;
  for (let x = 0; x < w; x++) {
    const v = lineBytes[x] ?? 0;
    const i = x * 4;
    d[i] = v; d[i + 1] = v; d[i + 2] = v; d[i + 3] = 255;
  }
  wefaxLiveCtx.putImageData(imgData, 0, y);
  wefaxLiveLineCount++;
}

// ── Live view: latest image card ────────────────────────────────────
function renderWefaxLatestCard() {
  if (!wefaxDom.liveLatest) return;
  if (wefaxImageHistory.length === 0) {
    wefaxDom.liveLatest.innerHTML =
      '<div style="color:var(--text-muted);font-size:0.82rem;">No images decoded yet. Enable the decoder and tune to a WEFAX station.</div>';
    return;
  }

  const img = wefaxImageHistory[0];
  if (!img) return;
  const ts = img._ts || '--';
  const date = img._tsMs ? new Date(img._tsMs).toLocaleDateString() : '';
  const meta = [
    `${String(img.ioc ?? '--')} IOC`,
    `${String(img.lpm ?? '--')} LPM`,
    `${String(img.line_count ?? 0)} lines`,
    `${date} ${ts}`,
  ].join(' \u00b7 ');

  const imgSrc = img._dataUrl
    ? img._dataUrl
    : img.path
      ? '/images/' + escapeHtml(img.path.split('/').pop())
      : null;

  let html = '<div class="sat-latest-card">';
  html += '<div class="sat-latest-title">Latest decoded image</div>';
  html += '<div class="sat-latest-meta">' + escapeHtml(meta) + '</div>';
  if (imgSrc) {
    html += '<a href="' + imgSrc + '" target="_blank" style="font-size:0.8rem;color:var(--accent);display:inline-block;margin-top:0.25rem;">View full image</a>';
  }
  html += '</div>';
  wefaxDom.liveLatest.innerHTML = html;
}

// ── History view: table ─────────────────────────────────────────────
function getWefaxFilteredHistory() {
  let items = wefaxImageHistory;

  if (wefaxFilterText) {
    items = items.filter(function (i) {
      const haystack = [
        String(i.ioc || ''),
        String(i.lpm || ''),
        String(i.line_count || ''),
      ].join(' ').toUpperCase();
      return haystack.indexOf(wefaxFilterText) >= 0;
    });
  }

  const sortVal = wefaxDom.sortSelect ? wefaxDom.sortSelect.value : 'newest';
  if (sortVal === 'oldest') items = items.slice().reverse();

  return items;
}

function renderWefaxHistoryRow(img: WefaxImage): HTMLElement {
  const row = document.createElement('div');
  row.className = 'sat-history-row';

  const ts = img._ts || '--';
  const date = img._tsMs ? new Date(img._tsMs).toLocaleDateString([], { month: 'short', day: 'numeric' }) : '';
  const ioc = img.ioc || '--';
  const lpm = img.lpm || '--';
  const lines = img.line_count || 0;

  const imgSrc = img._dataUrl
    ? img._dataUrl
    : img.path
      ? '/images/' + escapeHtml(img.path.split('/').pop())
      : null;
  const link = imgSrc
    ? '<a href="' + imgSrc + '" target="_blank" style="color:var(--accent);">View</a>'
    : '--';

  row.innerHTML = [
    '<span>' + escapeHtml(date + ' ' + ts) + '</span>',
    '<span>' + escapeHtml(String(ioc)) + '</span>',
    '<span>' + escapeHtml(String(lpm)) + '</span>',
    `<span>${String(lines)}</span>`,
    '<span>' + link + '</span>',
  ].join('');

  return row;
}

function renderWefaxHistoryTable() {
  if (!wefaxDom.historyList) return;
  pruneWefaxHistory();
  const items = getWefaxFilteredHistory();
  const fragment = document.createDocumentFragment();
  for (const item of items) {
    fragment.appendChild(renderWefaxHistoryRow(item));
  }
  wefaxDom.historyList.replaceChildren(fragment);

  if (wefaxDom.historyCount) {
    const total = wefaxImageHistory.length;
    const shown = items.length;
    wefaxDom.historyCount.textContent =
      total === 0
        ? 'No images yet'
        : shown === total
          ? `${String(total)} image${total === 1 ? '' : 's'}`
          : `${String(shown)} of ${String(total)} images`;
  }
}

// ── Add image to history ────────────────────────────────────────────
function addWefaxImage(msg: WefaxImage): void {
  const tsMs = Number.isFinite(msg.ts_ms) ? Number(msg.ts_ms) : Date.now();
  msg._tsMs = tsMs;
  msg._ts = new Date(tsMs).toLocaleTimeString([], {
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  });

  // Capture the live canvas as a data URI for thumbnails.
  const canvas = wefaxDom.liveCanvas;
  if (wefaxLiveCtx && canvas && wefaxLiveLineCount > 0) {
    const trimmed = wefaxLiveCtx.getImageData(0, 0, canvas.width, wefaxLiveLineCount);
    canvas.height = wefaxLiveLineCount;
    wefaxLiveCtx = canvas.getContext('2d');
    if (!wefaxLiveCtx) return;
    wefaxLiveCtx.putImageData(trimmed, 0, 0);
    try { msg._dataUrl = canvas.toDataURL('image/png'); } catch {}
  }

  wefaxImageHistory.unshift(msg);
  if (wefaxImageHistory.length > WEFAX_MAX_IMAGES) {
    wefaxImageHistory = wefaxImageHistory.slice(0, WEFAX_MAX_IMAGES);
  }

  scheduleWefaxUi('wefax-latest', renderWefaxLatestCard);
  if (wefaxActiveView === 'history') {
    scheduleWefaxUi('wefax-history', renderWefaxHistoryTable);
  }
}

// ── SSE event handlers (public API) ─────────────────────────────────
function onServerWefaxProgress(msg: WefaxProgress): void {
  // State-only update (no image data): show decoder state in status.
  if (msg.state && !msg.line_data) {
    if (wefaxDom.status) {
      wefaxDom.status.textContent = msg.state;
      // Highlight active states, dim idle/scanning.
      wefaxDom.status.style.color = msg.state.indexOf('Idle') === 0 ? '' : 'var(--text-accent)';
    }
    return;
  }

  if ((msg.line_count ?? 0) <= 1 || !wefaxLiveCtx) {
    resetLiveCanvas(msg.pixels_per_line || 1809);
  }

  if (msg.line_data) {
    const binary = atob(msg.line_data);
    const bytes = new Uint8Array(binary.length);
    for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
    paintLine(bytes);
  }

  if (wefaxDom.liveInfo) {
    wefaxDom.liveInfo.textContent =
      `Line ${String(msg.line_count ?? 0)} \u00b7 ${String(msg.ioc ?? '--')} IOC \u00b7 ${String(msg.lpm ?? '--')} LPM`;
  }
  if (wefaxDom.status) {
    wefaxDom.status.textContent = `Receiving \u2014 line ${String(msg.line_count ?? 0)}`;
    wefaxDom.status.style.color = 'var(--text-accent)';
  }
}

function onServerWefax(msg: WefaxImage): void {
  addWefaxImage(msg);

  if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = 'none';
  if (wefaxDom.status) {
    wefaxDom.status.textContent = `Complete \u2014 ${String(msg.line_count ?? 0)} lines`;
    wefaxDom.status.style.color = '';
  }
}

function restoreWefaxHistory(messages: WefaxImage[]): void {
  if (!messages.length) return;
  for (const message of messages) {
    const tsMs = Number.isFinite(message.ts_ms) ? Number(message.ts_ms) : Date.now();
    message._tsMs = tsMs;
    message._ts = new Date(tsMs).toLocaleTimeString([], {
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
    });
  }
  wefaxImageHistory = messages.concat(wefaxImageHistory);
  pruneWefaxHistory();
  scheduleWefaxUi('wefax-latest', renderWefaxLatestCard);
  if (wefaxActiveView === 'history') {
    scheduleWefaxUi('wefax-history', renderWefaxHistoryTable);
  }
}

function pruneWefaxHistoryView(): void {
  pruneWefaxHistory();
  renderWefaxHistoryTable();
  renderWefaxLatestCard();
}

function resetWefaxHistoryView(): void {
  wefaxImageHistory = [];
  if (wefaxDom.historyList) wefaxDom.historyList.innerHTML = '';
  if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = 'none';
  wefaxLiveCtx = null;
  wefaxLiveLineCount = 0;
  renderWefaxLatestCard();
  renderWefaxHistoryTable();
  if (wefaxDom.status) {
    wefaxDom.status.textContent = 'Idle';
    wefaxDom.status.style.color = '';
  }
}

// ── Filter / sort handlers ──────────────────────────────────────────
if (wefaxDom.filterInput) {
  const filterInput = wefaxDom.filterInput;
  wefaxDom.filterInput.addEventListener('input', function () {
    wefaxFilterText = filterInput.value.trim().toUpperCase();
    scheduleWefaxUi('wefax-history', renderWefaxHistoryTable);
  });
}
if (wefaxDom.sortSelect) {
  wefaxDom.sortSelect.addEventListener('change', function () {
    scheduleWefaxUi('wefax-history', renderWefaxHistoryTable);
  });
}

// ── Toggle button sync ──────────────────────────────────────────────
// Sync the Enable/Disable button from the SSE state update.  This is
// belt-and-suspenders alongside app.js _decoderToggles — guarantees the
// WEFAX button always reflects the server state.
wefaxWindow.syncWefaxToggle = function (enabled: boolean) {
  if (!wefaxDom.toggleBtn) return;
  wefaxDom.toggleBtn.dataset.enabled = enabled ? 'true' : 'false';
  wefaxDom.toggleBtn.textContent = enabled ? 'Disable WEFAX' : 'Enable WEFAX';
  wefaxDom.toggleBtn.style.borderColor = enabled ? '#00d17f' : '';
  wefaxDom.toggleBtn.style.color = enabled ? '#00d17f' : '';
};

// ── Button handlers ─────────────────────────────────────────────────
if (wefaxDom.toggleBtn) {
  const toggleButton = wefaxDom.toggleBtn;
  wefaxDom.toggleBtn.addEventListener('click', () => { void (async () => {
    try {
      if (wefaxWindow.takeSchedulerControlForDecoderDisable) {
        await wefaxWindow.takeSchedulerControlForDecoderDisable(toggleButton);
      }
      await hostCore.postPath('/toggle_wefax_decode');
    } catch (e) {
      console.error('WEFAX toggle failed', e);
    }
  })(); });
}
if (wefaxDom.clearBtn) {
  wefaxDom.clearBtn.addEventListener('click', () => { void (async () => {
    try {
      await hostCore.postPath('/clear_wefax_decode');
      resetWefaxHistoryView();
    } catch (e) {
      console.error('WEFAX clear failed', e);
    }
  })(); });
}

// ── Initial render ──────────────────────────────────────────────────
renderWefaxLatestCard();

wefaxWindow.trxPluginRuntime.registerDecoder({
  id: "wefax",
  onMessage: onServerWefax,
  restore: restoreWefaxHistory,
  prune: pruneWefaxHistoryView,
  reset: resetWefaxHistoryView,
});
wefaxWindow.trxPluginRuntime.registerDecoder({
  id: "wefax_progress",
  onMessage: onServerWefaxProgress,
});
