/// Compact disclosure pattern for attachments — shared between all
/// item type forms (edit + view modes). Edit mode supports + attach
/// (uploads via SW) and × remove (defers blob delete until form save).
/// View mode supports ↓ download (decrypts via SW + browser download).
/// Image-mime rows lazy-load 16×16 thumbnails via object URLs;
/// teardownAttachmentsDisclosure() revokes them on view exit.

import { sendMessage, escapeHtml } from '../../shared/state';
import type { AttachmentRef, VaultSettings } from '../../shared/types';
import { GLYPH_TYPE_DOCUMENT } from '../../shared/glyphs';

export type DisclosureMode = 'edit' | 'view';

export interface AttachmentsDisclosureOpts {
  itemId: string;
  attachments: AttachmentRef[];
  mode: DisclosureMode;
  onChange?: (next: AttachmentRef[]) => void;  // edit mode only
}

const formatBytes = (n: number): string => {
  if (n < 1024) return `${n} B`;
  if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
  return `${(n / (1024 * 1024)).toFixed(1)} MB`;
};

const isImage = (mime: string): boolean => mime.startsWith('image/');

const objectUrlRegistry = new Map<string, string>();  // attachmentId → object URL

function teardownObjectUrls(): void {
  for (const url of objectUrlRegistry.values()) {
    URL.revokeObjectURL(url);
  }
  objectUrlRegistry.clear();
}

async function fetchThumbUrl(itemId: string, attachmentId: string, mime: string): Promise<string | null> {
  if (objectUrlRegistry.has(attachmentId)) return objectUrlRegistry.get(attachmentId)!;
  const resp = await sendMessage({ type: 'download_attachment', itemId, attachmentId });
  if (!resp || !resp.ok) return null;
  const data = resp.data as { bytes: ArrayBuffer };
  const blob = new Blob([data.bytes], { type: mime });
  const url = URL.createObjectURL(blob);
  objectUrlRegistry.set(attachmentId, url);
  return url;
}

export function renderAttachmentsDisclosure(opts: AttachmentsDisclosureOpts): string {
  const count = opts.attachments.length;
  const headerLabel = count === 0 ? 'attachments' : `attachments (${count})`;
  const expanded = count > 0;
  const rowsHtml = opts.attachments.map((a) => {
    const action = opts.mode === 'edit' ? '×' : '↓';
    const actionClass = opts.mode === 'edit' ? 'attachment-row__remove' : 'attachment-row__download';
    const iconHtml = isImage(a.mime_type)
      ? `<span class="attachment-row__thumb" data-att-id="${escapeHtml(a.id)}" data-mime="${escapeHtml(a.mime_type)}">${GLYPH_TYPE_DOCUMENT}</span>`
      : `<span class="attachment-row__icon">${GLYPH_TYPE_DOCUMENT}</span>`;
    return `
      <div class="attachment-row" data-att-id="${escapeHtml(a.id)}">
        ${iconHtml}
        <span class="attachment-row__name">${escapeHtml(a.filename)}</span>
        <span class="attachment-row__meta">${formatBytes(a.size)}</span>
        <span class="${actionClass}" data-att-id="${escapeHtml(a.id)}">${action}</span>
      </div>
    `;
  }).join('');
  const addBtn = opts.mode === 'edit'
    ? `<button class="attachment-add-btn" type="button">+ attach file</button>`
    : '';
  const fileInput = opts.mode === 'edit'
    ? `<input type="file" class="attachments-disclosure__file-input" hidden />`
    : '';
  return `
    <details class="attachments-disclosure" ${expanded ? 'open' : ''}>
      <summary>${expanded ? '▾' : '▸'} ${headerLabel}</summary>
      <div class="attachments-disclosure__body">
        ${rowsHtml}
        ${addBtn}
      </div>
      ${fileInput}
    </details>
  `;
}

/// Attach event listeners to a disclosure already in the DOM (rendered
/// by `renderAttachmentsDisclosure`).
///
/// **Contract: call once per DOM instance.** Each invocation adds new
/// listeners; calling this twice on the same `<details>` element will
/// fire onChange twice per click. The standard re-render pattern is:
/// 1. Replace `disc.outerHTML` with a fresh `renderAttachmentsDisclosure(...)` call
/// 2. Then call `wireAttachmentsDisclosure(...)` to attach handlers to the NEW DOM
/// (Old listeners are GC'd when the previous `<details>` element is detached.)
export function wireAttachmentsDisclosure(
  root: HTMLElement,
  opts: AttachmentsDisclosureOpts,
): void {
  const disc = root.querySelector('.attachments-disclosure') as HTMLDetailsElement | null;
  if (!disc) return;

  // Lazy-load image thumbs whenever disclosure opens.
  const loadThumbs = async (): Promise<void> => {
    const thumbs = disc.querySelectorAll<HTMLElement>('.attachment-row__thumb');
    for (const thumb of thumbs) {
      const attId = thumb.dataset.attId;
      const mime = thumb.dataset.mime;
      if (!attId || !mime) continue;
      const url = await fetchThumbUrl(opts.itemId, attId, mime);
      if (url) {
        thumb.innerHTML = `<img src="${url}" alt="" />`;
      }
    }
  };
  if (disc.open) loadThumbs().catch(() => { /* swallow: thumb failures are non-fatal */ });
  disc.addEventListener('toggle', () => {
    if (disc.open) loadThumbs().catch(() => { /* swallow: thumb failures are non-fatal */ });
    else teardownObjectUrls();
  });

  // Edit mode: + attach file
  if (opts.mode === 'edit') {
    const fileInput = disc.querySelector('.attachments-disclosure__file-input') as HTMLInputElement | null;
    const addBtn = disc.querySelector('.attachment-add-btn') as HTMLButtonElement | null;
    addBtn?.addEventListener('click', () => fileInput?.click());

    fileInput?.addEventListener('change', async () => {
      const file = fileInput.files?.[0];
      if (!file) return;

      // Cap enforcement (popup-side, before sending to SW).
      const settingsResp = await sendMessage({ type: 'get_vault_settings' });
      if (settingsResp && settingsResp.ok) {
        const settings = (settingsResp.data as { settings: VaultSettings }).settings;
        const caps = settings.attachment_caps;
        if (caps?.per_attachment_max_bytes && file.size > caps.per_attachment_max_bytes) {
          alert(`file too large (${formatBytes(file.size)} / cap ${formatBytes(caps.per_attachment_max_bytes)})`);
          fileInput.value = '';
          return;
        }
        if (caps?.per_item_max_count && opts.attachments.length + 1 > caps.per_item_max_count) {
          alert(`item attachment count would exceed cap (${opts.attachments.length + 1} / ${caps.per_item_max_count})`);
          fileInput.value = '';
          return;
        }
      }

      const bytes = await file.arrayBuffer();
      const resp = await sendMessage({
        type: 'upload_attachment',
        itemId: opts.itemId,
        filename: file.name,
        mimeType: file.type || 'application/octet-stream',
        bytes,
      });
      if (resp && resp.ok) {
        const data = resp.data as { attachment: AttachmentRef };
        opts.onChange?.([...opts.attachments, data.attachment]);
      } else {
        alert(`upload failed: ${resp?.error ?? 'service worker unavailable'}`);
      }
      fileInput.value = '';  // allow re-pick of same file later
    });

    // Remove (×) buttons — defer the actual blob delete until form save
    disc.querySelectorAll<HTMLElement>('.attachment-row__remove').forEach((btn) => {
      btn.addEventListener('click', (e) => {
        e.preventDefault();
        const attId = btn.dataset.attId;
        if (!attId) return;
        opts.onChange?.(opts.attachments.filter((a) => a.id !== attId));
      });
    });
  }

  // View mode: ↓ download
  if (opts.mode === 'view') {
    disc.querySelectorAll<HTMLElement>('.attachment-row__download').forEach((btn) => {
      btn.addEventListener('click', async (e) => {
        e.preventDefault();
        const attId = btn.dataset.attId;
        if (!attId) return;
        const att = opts.attachments.find((a) => a.id === attId);
        if (!att) return;
        const resp = await sendMessage({ type: 'download_attachment', itemId: opts.itemId, attachmentId: attId });
        if (!resp || !resp.ok) {
          alert(`download failed: ${resp?.error ?? 'service worker unavailable'}`);
          return;
        }
        const data = resp.data as { bytes: ArrayBuffer; filename: string; mimeType: string };
        const blob = new Blob([data.bytes], { type: data.mimeType });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = data.filename;
        document.body.appendChild(a);
        a.click();
        a.remove();
        setTimeout(() => URL.revokeObjectURL(url), 5000);
      });
    });
  }
}

/// Call from the parent component's teardown to release any image thumbs.
export function teardownAttachmentsDisclosure(): void {
  teardownObjectUrls();
}
