/// Login type detail + form. Reference implementation for the shared
/// field helpers introduced in Slice 2.

import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../../shared/state';
import { renderFormHeader } from '../form-header';
import { REQUIRED_PILL_HTML } from '../../../shared/glyphs';
import type { Item, ItemId, LoginCore, ManifestEntry, Section, TotpConfig, AttachmentRef } from '../../../shared/types';
import { DEFAULT_PASSWORD_REQUEST } from '../../../shared/types';
import { base32Decode, base32Encode } from '../../../shared/base32';
import {
  renderRow,
  renderConcealedRow,
  renderSignatureBlock,
  wireFieldHandlers,
  renderSections,
  renderSectionsEditor,
  wireSectionsEditor,
} from '../fields';
import { openGeneratorPanel, closeGeneratorPanel, isGeneratorPanelOpen } from '../generator-panel';
import {
  renderAttachmentsDisclosure,
  wireAttachmentsDisclosure,
  teardownAttachmentsDisclosure,
} from '../attachments-disclosure';
import { wireFillFromTab, wireHostnameChip } from '../../../shared/form-affordances/url-tools';
import { wireGroupAutocomplete } from '../../../shared/form-affordances/group-autocomplete';
import { wirePasswordReveal, wirePasswordStrength } from '../../../shared/form-affordances/password-tools';
import { wireTotpPreview, wireTotpQr } from '../../../shared/form-affordances/totp-tools';
import { wireNotesMonoToggle } from '../../../shared/form-affordances/notes-tools';
import { scheduleRate } from '../../../setup/setup-helpers';

/// Sets a generated password on an input, reveals it as plain text, then
/// dispatches a synthetic InputEvent so listeners (e.g. the strength meter)
/// re-evaluate the new value.
export function applyGeneratedPassword(input: HTMLInputElement, value: string): void {
  input.value = value;
  input.type = 'text';
  input.dispatchEvent(new InputEvent('input', { bubbles: true }));
}

/// Called by the dispatcher before each render. Stops any in-flight
/// tickers / intervals / listeners the previous view may have attached.
export function teardown(): void {
  for (const fn of pendingAffordanceTeardowns) {
    try { fn(); } catch { /* best effort */ }
  }
  pendingAffordanceTeardowns = [];
  teardownAttachmentsDisclosure();
  stopTotpTicker();
  if (activeKeyHandler) {
    document.removeEventListener('keydown', activeKeyHandler);
    activeKeyHandler = null;
  }
  if (activeFormEscHandler) {
    document.removeEventListener('keydown', activeFormEscHandler);
    activeFormEscHandler = null;
  }
  sectionsExpanded = false;
  closeGeneratorPanel();
}

// ----------------------------------------------------------------------
// Detail view
// ----------------------------------------------------------------------

export async function renderDetail(app: HTMLElement, item: Item): Promise<void> {
  if (item.core.type !== 'login') return;
  const core = item.core as LoginCore & { type: 'login' };
  const password = core.password ?? '';
  const username = core.username ?? '';
  const url = core.url ?? '';
  const hasTotp = core.totp !== undefined;

  const sigInner = `
    <div style="display:flex;justify-content:space-between;align-items:center;">
      <div style="font-size:14px;font-weight:600;color:#c9d1d9;">${escapeHtml(item.title)}</div>
      ${url ? `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" style="font-size:11px;color:#d2ab43;">open ↗</a>` : ''}
    </div>
  `;

  app.innerHTML = `
    <div class="pad">
      <div style="margin-bottom:12px;">
        ${renderSignatureBlock({ accent: 'gold', children: sigInner })}
      </div>
      ${username ? renderRow({ label: 'username', value: username, copyable: true }) : ''}
      ${renderConcealedRow({ id: 'login-password', label: 'password', value: password, kind: 'password' })}
      ${url ? renderRow({ label: 'url', value: url, href: url }) : ''}
      ${hasTotp ? `
        <div class="field-row">
          <span class="field-row__label">totp</span>
          <span class="field-row__value monospace" id="totp-code">…</span>
          <span class="field-row__actions"><span id="totp-countdown" style="font-variant-numeric:tabular-nums;">…</span></span>
        </div>
      ` : ''}
      ${item.notes ? renderRow({ label: 'notes', value: item.notes, multiline: true }) : ''}
      ${renderSections(item, 'login')}
      ${renderAttachmentsDisclosure({ itemId: item.id, attachments: item.attachments, mode: 'view' })}
      <div class="form-actions" style="margin-top:14px;">
        <button class="btn" id="back-btn">back</button>
        ${Object.keys(item.field_history).length > 0 ? '<button class="btn" id="history-btn">view history</button>' : ''}
        <button class="btn" id="edit-btn">edit</button>
        <button class="btn" id="fill-btn">autofill</button>
        <button class="btn danger" id="trash-btn">trash</button>
      </div>
    </div>
  `;

  wireFieldHandlers(app);
  wireAttachmentsDisclosure(app, { itemId: item.id, attachments: item.attachments, mode: 'view' });

  document.getElementById('back-btn')?.addEventListener('click', () => {
    teardown();
    navigate('list');
  });
  document.getElementById('history-btn')?.addEventListener('click', () => {
    setState({ historyItemId: item.id });
    navigate('field-history');
  });
  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');
  });
  document.getElementById('fill-btn')?.addEventListener('click', async () => {
    const { capturedTabId, capturedUrl } = getState();
    if (capturedTabId === null) { setState({ error: 'No active tab captured' }); return; }
    const resp = await sendMessage({
      type: 'fill_credentials', id: item.id, capturedTabId, capturedUrl,
    });
    if (!resp.ok) setState({ error: resp.error });
    else { teardown(); window.close(); }
  });

  if (hasTotp) startTotpTicker(item.id);

  const handler = async (e: KeyboardEvent) => {
    // Bail if the user is typing in an editable field — don't steal printable keystrokes.
    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 'c':
        if (username) {
          try { await navigator.clipboard.writeText(username); } catch { /* no-op */ }
        }
        break;

      case 'p':
        try { await navigator.clipboard.writeText(password); } catch { /* no-op */ }
        break;

      case 't':
        if (hasTotp) {
          const codeEl = document.getElementById('totp-code');
          const code = codeEl?.textContent?.trim();
          if (code && code !== '…') {
            try { await navigator.clipboard.writeText(code); } catch { /* no-op */ }
          }
        }
        break;

      case 'f': {
        const { capturedTabId, capturedUrl } = getState();
        if (capturedTabId === null) { setState({ error: 'No active tab captured' }); break; }
        const resp = await sendMessage({
          type: 'fill_credentials', id: item.id, capturedTabId, capturedUrl,
        });
        if (!resp.ok) setState({ error: resp.error });
        else { teardown(); window.close(); }
        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);
}

// ----------------------------------------------------------------------
// TOTP ticker
// ----------------------------------------------------------------------

let totpTickerId: ReturnType<typeof setInterval> | null = null;
let activeKeyHandler: ((e: KeyboardEvent) => void) | null = null;
let activeFormEscHandler: ((e: KeyboardEvent) => void) | null = null;
let sectionsExpanded = false;
let pendingAffordanceTeardowns: Array<() => void> = [];
function stopTotpTicker(): void {
  if (totpTickerId !== null) { clearInterval(totpTickerId); totpTickerId = null; }
}
function startTotpTicker(id: ItemId): void {
  stopTotpTicker();
  const tick = async () => {
    const r = await sendMessage({ type: 'get_totp', id });
    if (!r.ok) return;
    const { code, expires_at } = r.data as { code: string; expires_at: number };
    const codeEl = document.getElementById('totp-code');
    const cdEl = document.getElementById('totp-countdown');
    if (codeEl) codeEl.textContent = code;
    if (cdEl) cdEl.textContent = `${Math.max(0, expires_at - Math.floor(Date.now() / 1000))}s`;
  };
  void tick();
  totpTickerId = setInterval(() => void tick(), 1000);
}

// ----------------------------------------------------------------------
// Form (add / edit)
// ----------------------------------------------------------------------

export interface RenderFormOptions {
  surface?: 'popup' | 'fullscreen';
  /** When true, renderForm skips its own save/cancel buttons (caller provides them in a sticky bar). */
  externalActions?: boolean;
}

export function renderForm(
  app: HTMLElement,
  mode: 'add' | 'edit',
  existing: Item | null,
  opts: RenderFormOptions = {}
): void {
  const surface = opts.surface ?? 'popup';
  const externalActions = opts.externalActions ?? false;
  const state = getState();
  const existingCore = (existing?.core.type === 'login')
    ? (existing.core as LoginCore & { type: 'login' })
    : null;

  const title = existing?.title ?? '';
  const url = existingCore?.url ?? '';
  const username = existingCore?.username ?? '';
  const password = existingCore?.password ?? '';
  const totpStr = existingCore?.totp ? base32Encode(new Uint8Array(existingCore.totp.secret)) : '';
  const group = existing?.group ?? '';
  const notes = existing?.notes ?? '';

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

  const titleFieldHtml = `
    <div class="form-group"><label class="label" for="f-title">title ${REQUIRED_PILL_HTML}</label>
      <input id="f-title" type="text" value="${escapeHtml(title)}" placeholder="GitHub"></div>`;

  const urlFieldHtml = `
    <div class="form-group">
      <label class="label" for="f-url">url</label>
      <div class="inline-row">
        <input id="f-url" type="text" value="${escapeHtml(url)}" placeholder="https://github.com/login">
        <button id="fill-from-tab-btn" class="glyph-btn" type="button" title="fill from active tab">⤓</button>
      </div>
      <div id="hostname-chip-row" class="hostname-chip-row" hidden></div>
    </div>`;

  const groupFieldHtml = `
    <div class="form-group"><label class="label" for="f-group">group</label>
      <input id="f-group" type="text" value="${escapeHtml(group)}" placeholder="work"></div>`;

  const usernameFieldHtml = `
    <div class="form-group"><label class="label" for="f-username">username</label>
      <input id="f-username" type="text" value="${escapeHtml(username)}" placeholder="alice@example.com"></div>`;

  const passwordFieldHtml = `
    <div class="form-group">
      <label class="label" for="f-password">password</label>
      <div class="inline-row">
        <input id="f-password" type="password" value="${escapeHtml(password)}">
        <button id="reveal-password-btn" class="glyph-btn" type="button" title="reveal">⊙</button>
        <button class="gen-trigger" id="gen-btn" type="button" title="generate password" aria-expanded="false">↻</button>
      </div>
      <div id="strength-bar-row" class="strength-bar-row" hidden>
        <div class="strength-bar"><span></span><span></span><span></span><span></span><span></span></div>
        <div class="strength-label"></div>
      </div>
    </div>`;

  const totpFieldHtml = `
    <div class="form-group">
      <label class="label" for="f-totp">totp secret (base32)</label>
      <div class="inline-row">
        <input id="f-totp" type="text" value="${escapeHtml(totpStr)}" placeholder="JBSWY3DPEHPK3PXP">
        <button id="totp-qr-btn" class="glyph-btn" type="button" title="paste / upload QR">◫</button>
      </div>
      <div id="totp-preview-row" class="totp-preview" hidden>
        <span class="totp-code">…</span>
        <span class="totp-countdown">…</span>
      </div>
      <div id="totp-qr-panel" class="totp-qr-panel" hidden>
        <input id="totp-qr-file" type="file" accept="image/*" />
        <div style="font-size:10px;color:var(--text-dim,#6b7888);margin-top:4px;">paste image, drop image, or pick a file</div>
        <div id="totp-qr-error" class="totp-qr-error"></div>
      </div>
    </div>`;

  const identityHtml = `
    <div data-form-section="identity" class="${surface === 'fullscreen' ? 'glass form-col' : ''}">
      ${surface === 'fullscreen' ? '<div class="col-header">Identity</div>' : ''}
      ${titleFieldHtml}
      ${urlFieldHtml}
      ${groupFieldHtml}
    </div>`;

  const credentialsHtml = `
    <div data-form-section="credentials" class="${surface === 'fullscreen' ? 'glass form-col' : ''}">
      ${surface === 'fullscreen' ? '<div class="col-header">Credentials</div>' : ''}
      ${usernameFieldHtml}
      ${passwordFieldHtml}
      ${totpFieldHtml}
    </div>`;

  const sectionsHtml = surface === 'fullscreen'
    ? `<div class="form-grid">${identityHtml}${credentialsHtml}</div>`
    : `${identityHtml}${credentialsHtml}`;

  app.innerHTML = `
    <div class="pad">
      ${surface === 'popup' ? renderFormHeader({ titleText: mode === 'add' ? 'new login' : 'edit login' }) : ''}
      ${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}

      ${sectionsHtml}

      <div class="${surface === 'fullscreen' ? 'form-lower' : ''}">
        <div class="form-group">
          <div class="notes-with-toggle">
            <label class="label" for="f-notes" style="margin:0;flex:1;">notes</label>
            <button id="notes-mono-btn" class="glyph-btn" type="button" title="toggle monospace">≡</button>
          </div>
          <textarea id="f-notes" placeholder="recovery codes, security questions...">${escapeHtml(notes)}</textarea>
        </div>

        ${renderSectionsEditor(sectionsDraft, sectionsExpanded)}
        ${isInTab() ? renderAttachmentsDisclosure({ itemId: existing?.id ?? '', attachments: attachmentsDraft, mode: 'edit' }) : ''}
        <div class="form-actions" ${externalActions ? 'hidden' : ''}>
          <button class="btn" id="cancel-btn">cancel</button>
          <button class="btn btn-primary" id="save-btn">${state.loading ? '<span class="spinner"></span>' : 'save'}</button>
        </div>
      </div>
    </div>
  `;

  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);

  if (isInTab()) {
    const wireDisclosure = (): void => {
      wireAttachmentsDisclosure(app, {
        itemId: existing?.id ?? '',
        attachments: attachmentsDraft,
        mode: 'edit',
        onChange: (next) => {
          attachmentsDraft = next;
          const disc = app.querySelector('.attachments-disclosure');
          if (disc) {
            disc.outerHTML = renderAttachmentsDisclosure({
              itemId: existing?.id ?? '',
              attachments: attachmentsDraft,
              mode: 'edit',
            });
            wireDisclosure();
          }
        },
      });
    };
    wireDisclosure();
  }

  // ---- Smart input affordances ------------------------------------------
  // Each wireXxx call attaches event listeners to the just-rendered form.
  // Affordances that hold timers/intervals return a teardown fn we collect
  // here and run from the form's existing teardown() entry point.
  const affordanceTeardowns: Array<() => void> = [];

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const sm = sendMessage as any;
  wireFillFromTab(app, { sendMessage: sm });
  wireHostnameChip(app);
  void wireGroupAutocomplete(app, { sendMessage: sm });
  affordanceTeardowns.push(wirePasswordReveal(app));
  wirePasswordStrength(app, { scheduleRate });
  affordanceTeardowns.push(wireTotpPreview(app, { sendMessage: sm }));
  wireTotpQr(app);
  void wireNotesMonoToggle(app, { itemId: existing?.id ?? '' });

  // Stash teardown-runner so the existing `teardown()` calls it.
  pendingAffordanceTeardowns = affordanceTeardowns;

  document.getElementById('gen-btn')?.addEventListener('click', (e) => {
    const trigger = e.currentTarget as HTMLElement;
    if (isGeneratorPanelOpen()) {
      closeGeneratorPanel();
      return;
    }
    const passwordRow = trigger.closest('.form-group') as HTMLElement | null;
    if (!passwordRow) return;
    const initial = getState().generatorDefaults ?? DEFAULT_PASSWORD_REQUEST;
    openGeneratorPanel({
      parent: passwordRow,  // panel mounts inside the password form-group
      trigger,
      initial,
      context: 'fill-field',
      onPicked: (value) => {
        const pw = document.getElementById('f-password') as HTMLInputElement | null;
        if (pw) applyGeneratedPassword(pw, value);
      },
    });
  });

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

  document.getElementById('popout-btn')?.addEventListener('click', popOutToTab);

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

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

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

function normalizeUrl(raw: string): { ok: true; value: string } | { ok: false; error: string } {
  if (!raw) return { ok: true, value: '' };
  const trimmed = raw.trim();
  const candidate = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
  try {
    const u = new URL(candidate);
    if (!u.host) return { ok: false, error: 'URL must include a host (e.g. https://example.com)' };
    return { ok: true, value: u.toString() };
  } catch {
    return { ok: false, error: 'URL is not valid — try something like https://example.com' };
  }
}

export async function saveLogin(mode: 'add' | 'edit', existing: Item | null, sectionsDraft: Section[], attachmentsDraft: AttachmentRef[]): Promise<void> {
  const state = getState();
  const title = (document.getElementById('f-title') as HTMLInputElement).value.trim();
  const rawUrl = (document.getElementById('f-url') as HTMLInputElement).value;
  const username = (document.getElementById('f-username') as HTMLInputElement).value.trim();
  const password = (document.getElementById('f-password') as HTMLInputElement).value;
  const totpStr = (document.getElementById('f-totp') as HTMLInputElement).value.trim();
  const group = (document.getElementById('f-group') as HTMLInputElement).value.trim();
  const notes = (document.getElementById('f-notes') as HTMLTextAreaElement).value;

  if (!title) { setState({ error: 'Title is required' }); return; }

  const urlResult = normalizeUrl(rawUrl);
  if (!urlResult.ok) { setState({ error: urlResult.error }); return; }
  const url = urlResult.value;

  let totp: TotpConfig | undefined;
  if (totpStr) {
    try {
      const bytes = base32Decode(totpStr);
      totp = {
        secret: Array.from(bytes),
        algorithm: 'sha1', digits: 6, period_seconds: 30, kind: 'totp',
      };
    } catch (err) {
      setState({ error: `Invalid TOTP secret: ${err instanceof Error ? err.message : String(err)}` });
      return;
    }
  }

  const now = Math.floor(Date.now() / 1000);
  const core: LoginCore & { type: 'login' } = {
    type: 'login',
    username: username || undefined,
    password: password || undefined,
    url: url || undefined,
    totp,
  };

  const item: Item = {
    id: existing?.id ?? '',
    title, type: 'login',
    tags: existing?.tags ?? [],
    favorite: existing?.favorite ?? false,
    group: group || undefined,
    notes: notes || 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 });
  }
}
