/// Document item type — title + REQUIRED primary attachment + optional
/// notes/tags + optional supplementary attachments.
/// Primary attachment is referenced by ID from the item's attachments array.

import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../../shared/state';
import { renderFormHeader } from '../form-header';
import { REQUIRED_PILL_HTML, GLYPH_TYPE_DOCUMENT, GLYPH_PREVIEW } from '../../../shared/glyphs';
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
import {
  renderSectionsEditor, wireSectionsEditor,
} from '../fields';
import {
  renderAttachmentsDisclosure,
  wireAttachmentsDisclosure,
  teardownAttachmentsDisclosure,
} from '../attachments-disclosure';

let activeKeyHandler: ((e: KeyboardEvent) => void) | null = null;
let activeFormEscHandler: ((e: KeyboardEvent) => void) | null = null;
let sectionsExpanded = false;

const detailObjectUrls = new Set<string>();

function trackDetailUrl(url: string): string {
  detailObjectUrls.add(url);
  return url;
}

function revokeAllDetailUrls(): void {
  for (const url of detailObjectUrls) {
    URL.revokeObjectURL(url);
  }
  detailObjectUrls.clear();
}

export function teardown(): void {
  teardownAttachmentsDisclosure();
  revokeAllDetailUrls();
  if (activeKeyHandler) {
    document.removeEventListener('keydown', activeKeyHandler);
    activeKeyHandler = null;
  }
  if (activeFormEscHandler) {
    document.removeEventListener('keydown', activeFormEscHandler);
    activeFormEscHandler = null;
  }
  sectionsExpanded = false;
}

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`;
};

export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Item | null): void {
  teardown();
  const state = getState();
  const isEdit = mode === 'edit';
  const c = existing?.core.type === 'document' ? existing.core : null;

  const sectionsDraft: Section[] = existing
    ? JSON.parse(JSON.stringify(existing.sections)) as Section[]
    : [];
  let attachmentsDraft: AttachmentRef[] = existing?.attachments ?? [];
  let primaryId: string = c?.primary_attachment ?? '';

  const renderPrimary = (): string => {
    const primaryRef = attachmentsDraft.find((a) => a.id === primaryId);
    if (!primaryRef) {
      return `
        <div class="document-primary-row document-primary-row--empty" id="primary-picker">
          + attach primary file
        </div>
      `;
    }
    return `
      <div class="document-primary-row" id="primary-picker">
        <span class="document-primary-row__thumb">${GLYPH_TYPE_DOCUMENT}</span>
        <span class="document-primary-row__name">${escapeHtml(primaryRef.filename)}</span>
        <span class="document-primary-row__meta">${formatBytes(primaryRef.size)}</span>
        <span class="document-primary-row__action">↑ change</span>
      </div>
    `;
  };

  app.innerHTML = `
    <div class="pad">
      ${renderFormHeader({ titleText: isEdit ? 'edit document' : 'new document' })}
      ${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
      <div class="form-group">
        <label class="label" for="f-title">title ${REQUIRED_PILL_HTML}</label>
        <input id="f-title" type="text" value="${escapeHtml(existing?.title ?? '')}" placeholder="Passport, Lease, etc.">
      </div>
      <div class="form-group">
        <label class="label">primary attachment ${REQUIRED_PILL_HTML}</label>
        ${renderPrimary()}
        <input type="file" id="primary-file-input" hidden />
      </div>
      <div class="form-group">
        <label class="label" for="f-notes">notes</label>
        <textarea id="f-notes" rows="3" placeholder="optional context...">${escapeHtml(existing?.notes ?? '')}</textarea>
      </div>
      <div class="form-group">
        <label class="label" for="f-tags">tags</label>
        <input id="f-tags" type="text" value="${escapeHtml((existing?.tags ?? []).join(', '))}" placeholder="legal, official">
      </div>
      ${renderSectionsEditor(sectionsDraft, sectionsExpanded)}
      ${renderAttachmentsDisclosure({ itemId: existing?.id ?? '', attachments: attachmentsDraft, mode: 'edit' })}
      <div class="form-actions">
        <button class="btn" id="cancel-btn">cancel</button>
        <button class="btn btn-primary" id="save-btn">save</button>
      </div>
    </div>
  `;

  const wirePrimaryPicker = (): void => {
    const picker = document.getElementById('primary-picker');
    const fileInput = document.getElementById('primary-file-input') as HTMLInputElement | null;
    picker?.addEventListener('click', () => fileInput?.click());
    fileInput?.addEventListener('change', async () => {
      const file = fileInput.files?.[0];
      if (!file) return;
      const bytes = await file.arrayBuffer();
      const resp = await sendMessage({
        type: 'upload_attachment',
        itemId: existing?.id ?? '',
        filename: file.name,
        mimeType: file.type || 'application/octet-stream',
        bytes,
      });
      if (resp && resp.ok) {
        const data = resp.data as { attachment: AttachmentRef };
        attachmentsDraft = [...attachmentsDraft.filter((a) => a.id !== primaryId), data.attachment];
        primaryId = data.attachment.id;
        const picker2 = document.getElementById('primary-picker');
        if (picker2) {
          picker2.outerHTML = renderPrimary();
          wirePrimaryPicker();
        }
      } else {
        alert(`upload failed: ${resp?.error ?? 'service worker unavailable'}`);
      }
      fileInput.value = '';
    });
  };
  wirePrimaryPicker();

  const rerender = (): void => {
    const disclosure = app.querySelector('.disclosure');
    if (!disclosure) return;
    sectionsExpanded = disclosure.getAttribute('data-expanded') === 'true';
    disclosure.outerHTML = renderSectionsEditor(sectionsDraft, sectionsExpanded);
    wireSectionsEditor(app, sectionsDraft, rerender);
  };
  wireSectionsEditor(app, sectionsDraft, rerender);

  const wireDisclosure = (): void => {
    wireAttachmentsDisclosure(app, {
      itemId: existing?.id ?? '',
      attachments: attachmentsDraft,
      mode: 'edit',
      onChange: (next) => {
        attachmentsDraft = next;
        // If the user removed the primary, clear primaryId so save validation will block
        if (!attachmentsDraft.find((a) => a.id === primaryId)) primaryId = '';
        const disc = app.querySelector('.attachments-disclosure');
        if (disc) {
          disc.outerHTML = renderAttachmentsDisclosure({
            itemId: existing?.id ?? '',
            attachments: attachmentsDraft,
            mode: 'edit',
          });
          wireDisclosure();
        }
      },
    });
  };
  wireDisclosure();

  document.getElementById('cancel-btn')?.addEventListener('click', () => {
    setState({ error: null });
    navigate(isEdit ? 'detail' : 'list');
  });
  document.getElementById('popout-btn')?.addEventListener('click', popOutToTab);

  document.getElementById('save-btn')?.addEventListener('click', async () => {
    await saveDocument(mode, existing, primaryId, attachmentsDraft, sectionsDraft);
  });

  const escHandler = (e: KeyboardEvent) => {
    if (e.key === 'Escape') {
      setState({ error: null });
      navigate(isEdit ? 'detail' : 'list');
    }
  };
  activeFormEscHandler = escHandler;
  document.addEventListener('keydown', escHandler);

  (document.getElementById('f-title') as HTMLInputElement | null)?.focus();
}

async function saveDocument(
  mode: 'add' | 'edit',
  existing: Item | null,
  primaryId: string,
  attachmentsDraft: AttachmentRef[],
  sectionsDraft: Section[],
): Promise<void> {
  const state = getState();
  const title = (document.getElementById('f-title') as HTMLInputElement).value.trim();
  if (!title) { setState({ error: 'Title is required' }); return; }

  const primaryRef = attachmentsDraft.find((a) => a.id === primaryId);
  if (!primaryId || !primaryRef) {
    setState({ error: 'primary attachment required' });
    return;
  }

  const notesRaw = (document.getElementById('f-notes') as HTMLTextAreaElement).value.trim();
  const tagsRaw  = (document.getElementById('f-tags') as HTMLInputElement).value;
  const tags = tagsRaw.split(',').map((t) => t.trim()).filter(Boolean);

  const core = {
    type: 'document' as const,
    filename: primaryRef.filename,
    mime_type: primaryRef.mime_type,
    primary_attachment: primaryId,
  };

  const now = Math.floor(Date.now() / 1000);
  const item: Item = {
    id: existing?.id ?? '',
    title,
    type: 'document',
    tags,
    favorite: existing?.favorite ?? false,
    group: existing?.group,
    notes: notesRaw || undefined,
    created: existing?.created ?? now,
    modified: now,
    trashed_at: undefined,
    core,
    sections: sectionsDraft,
    attachments: attachmentsDraft,
    field_history: existing?.field_history ?? {},
  };

  setState({ loading: true, error: null });
  const resp = mode === 'add'
    ? await sendMessage({ type: 'add_item', item })
    : await sendMessage({ type: 'update_item', id: state.selectedId!, item });
  if (resp.ok) {
    const listResp = await sendMessage({ type: 'list_items' });
    if (listResp.ok) {
      const data = listResp.data as { items: Array<[ItemId, ManifestEntry]> };
      navigate('list', { entries: data.items, selectedId: null, selectedItem: null });
    } else navigate('list');
  } else {
    setState({ loading: false, error: resp.error });
  }
}

export async function renderDetail(app: HTMLElement, item: Item): Promise<void> {
  teardown();
  if (item.core.type !== 'document') return;
  const c = item.core;
  const primaryRef = item.attachments.find((a) => a.id === c.primary_attachment);
  if (!primaryRef) {
    app.innerHTML = `<div class="pad"><p>document missing primary attachment</p></div>`;
    return;
  }

  const isImageMime = primaryRef.mime_type.startsWith('image/');
  const supplementary = item.attachments.filter((a) => a.id !== c.primary_attachment);

  const tagsHtml = item.tags && item.tags.length > 0
    ? `<div class="form-group"><label class="label">tags</label><span>${item.tags.map((t) => escapeHtml(t)).join(', ')}</span></div>`
    : '';

  app.innerHTML = `
    <div class="pad">
      <div class="detail-title" style="margin-bottom:12px;">${escapeHtml(item.title)}</div>

      <div class="document-signature-block" id="doc-sigblock">
        <div class="document-signature-block__thumb" data-att-id="${escapeHtml(primaryRef.id)}" data-mime="${escapeHtml(primaryRef.mime_type)}">${GLYPH_TYPE_DOCUMENT}</div>
        <div class="document-signature-block__info">
          <div class="document-signature-block__name">${escapeHtml(primaryRef.filename)}</div>
          <div class="document-signature-block__meta">${formatBytes(primaryRef.size)} · ${new Date(primaryRef.created * 1000).toISOString().slice(0, 10)}</div>
          <div class="document-signature-block__actions">
            <span id="doc-download" style="cursor:pointer;color:#d2ab43;">↓ download</span>
            ${isImageMime ? `<span id="doc-preview" style="cursor:pointer;color:#d2ab43;margin-left:10px;">${GLYPH_PREVIEW} preview</span>` : ''}
          </div>
        </div>
      </div>

      ${item.notes ? `<div class="form-group"><label class="label">notes</label><p>${escapeHtml(item.notes)}</p></div>` : ''}
      ${tagsHtml}

      ${supplementary.length > 0 ? renderAttachmentsDisclosure({ itemId: item.id, attachments: supplementary, mode: 'view' }) : ''}

      <div class="form-actions" style="margin-top:14px;">
        <button class="btn" id="back-btn">back</button>
        <button class="btn" id="edit-btn">edit</button>
        <button class="btn danger" id="trash-btn">trash</button>
      </div>
    </div>
  `;

  // Lazy-load thumb for image-mime primaries
  if (isImageMime) {
    const thumb = document.querySelector('.document-signature-block__thumb') as HTMLElement;
    sendMessage({ type: 'download_attachment', itemId: item.id, attachmentId: primaryRef.id }).then((resp) => {
      if (!resp || !resp.ok) return;
      const data = resp.data as { bytes: ArrayBuffer };
      const blob = new Blob([data.bytes], { type: primaryRef.mime_type });
      const url = trackDetailUrl(URL.createObjectURL(blob));
      if (thumb) thumb.innerHTML = `<img src="${url}" alt="" />`;
    }).catch(() => {});
  }

  document.getElementById('doc-download')?.addEventListener('click', async () => {
    const resp = await sendMessage({ type: 'download_attachment', itemId: item.id, attachmentId: primaryRef.id });
    if (!resp || !resp.ok) { alert(`download failed: ${resp?.error ?? 'service worker unavailable'}`); return; }
    const data = resp.data as { bytes: ArrayBuffer };
    const blob = new Blob([data.bytes], { type: primaryRef.mime_type });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url; a.download = primaryRef.filename;
    document.body.appendChild(a); a.click(); a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 5000);
  });

  document.getElementById('doc-preview')?.addEventListener('click', async () => {
    const sigblock = document.getElementById('doc-sigblock')!;
    const existingPreview = sigblock.querySelector('.document-signature-block__preview');
    if (existingPreview) {
      const img = existingPreview.querySelector('img');
      if (img?.src) {
        URL.revokeObjectURL(img.src);
        detailObjectUrls.delete(img.src);
      }
      existingPreview.remove();
      return;
    }
    const resp = await sendMessage({ type: 'download_attachment', itemId: item.id, attachmentId: primaryRef.id });
    if (!resp || !resp.ok) return;
    const data = resp.data as { bytes: ArrayBuffer };
    const blob = new Blob([data.bytes], { type: primaryRef.mime_type });
    const url = trackDetailUrl(URL.createObjectURL(blob));
    const preview = document.createElement('div');
    preview.className = 'document-signature-block__preview';
    preview.innerHTML = `<img src="${url}" alt="" />`;
    sigblock.appendChild(preview);
  });

  // Wire supplementary disclosure
  if (supplementary.length > 0) {
    wireAttachmentsDisclosure(app, { itemId: item.id, attachments: supplementary, mode: 'view' });
  }

  document.getElementById('back-btn')?.addEventListener('click', () => { teardown(); navigate('list'); });
  document.getElementById('edit-btn')?.addEventListener('click', () => { teardown(); navigate('edit'); });
  document.getElementById('trash-btn')?.addEventListener('click', async () => {
    if (!confirm(`Move "${item.title}" to trash?`)) return;
    teardown();
    const resp = await sendMessage({ type: 'delete_item', id: item.id });
    if (!resp.ok) { setState({ error: resp.error }); return; }
    const listResp = await sendMessage({ type: 'list_items' });
    if (listResp.ok) {
      const data = listResp.data as { items: Array<[ItemId, ManifestEntry]> };
      navigate('list', { entries: data.items, selectedId: null, selectedItem: null });
    } else navigate('list');
  });

  const handler = async (e: KeyboardEvent) => {
    const t = e.target;
    if (t instanceof HTMLElement) {
      const tag = t.tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || t.isContentEditable) return;
    }
    switch (e.key) {
      case 'Escape': teardown(); navigate('list'); break;
      case 'e':      teardown(); navigate('edit'); break;
      case 'd':
        e.preventDefault();
        if (confirm(`Move "${item.title}" to trash?`)) {
          teardown();
          const resp = await sendMessage({ type: 'delete_item', id: item.id });
          if (!resp.ok) { setState({ error: resp.error }); return; }
          const listResp = await sendMessage({ type: 'list_items' });
          if (listResp.ok) {
            const data = listResp.data as { items: Array<[ItemId, ManifestEntry]> };
            navigate('list', { entries: data.items, selectedId: null, selectedItem: null });
          } else navigate('list');
        }
        break;
    }
  };
  activeKeyHandler = handler;
  document.addEventListener('keydown', handler);
}
