/// Field rendering primitives used by every typed-item detail view.
///
/// Pure functions that return HTML strings. Caller is responsible for
/// mounting the strings into the DOM (typically via `app.innerHTML = ...`).
/// After mounting, call `wireFieldHandlers(scope)` once to bind reveal +
/// copy click handlers on any rendered rows.

import { escapeHtml } from '../../shared/state';
import { colorizePassword } from '../../shared/password-coloring';
import type { Item, Section, Field, FieldValue } from '../../shared/types';

export interface RowOpts {
  label: string;
  value: string;
  copyable?: boolean;
  href?: string;
  monospace?: boolean;
  multiline?: boolean;
}

/// Plain label/value row. Optional copy button, optional anchor wrap,
/// optional monospace styling, optional multiline (renders in a <pre>).
export function renderRow(opts: RowOpts): string {
  const { label, value, copyable, href, monospace, multiline } = opts;
  const valueClass = `field-row__value${monospace ? ' monospace' : ''}`;
  let valueHtml: string;
  if (multiline) {
    valueHtml = `<pre>${escapeHtml(value)}</pre>`;
  } else if (href) {
    valueHtml = `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml(value)}</a>`;
  } else {
    valueHtml = escapeHtml(value);
  }
  const actions = copyable
    ? `<button type="button" data-field-action="copy" data-field-value="${escapeHtml(value)}">copy</button>`
    : '';
  return `
    <div class="field-row">
      <span class="field-row__label">${escapeHtml(label)}</span>
      <span class="${valueClass}" data-field-role="value">${valueHtml}</span>
      <span class="field-row__actions">${actions}</span>
    </div>
  `;
}

export interface ConcealedRowOpts {
  id: string;
  label: string;
  value: string;
  kind?: 'password' | 'concealed';
  monospace?: boolean;
  multiline?: boolean;
}

/// Concealed row — value rendered hidden until the user clicks "show".
/// Plaintext is stored in `data-field-value` on the row element and copied
/// to the visible value span on reveal. Copy button always copies plaintext.
/// When `kind` is "password", wireFieldHandlers applies colorizePassword on
/// reveal so digits/symbols/letters are rendered in distinct colours.
export function renderConcealedRow(opts: ConcealedRowOpts): string {
  const { id, label, value, kind, monospace, multiline } = opts;
  const placeholder = multiline ? `•••• (${value.length} chars)` : '••••';
  const valueClass = `field-row__value${monospace ? ' monospace' : ''}`;
  const kindAttr = kind ? ` data-field-kind="${escapeHtml(kind)}"` : '';
  return `
    <div class="field-row" data-field-id="${escapeHtml(id)}" data-revealed="false" data-field-value="${escapeHtml(value)}" data-field-multiline="${multiline ? 'true' : 'false'}"${kindAttr}>
      <span class="field-row__label">${escapeHtml(label)}</span>
      <span class="${valueClass}" data-field-role="value">${escapeHtml(placeholder)}</span>
      <span class="field-row__actions">
        <button type="button" data-field-action="reveal">show</button>
        <button type="button" data-field-action="copy" data-field-value="${escapeHtml(value)}">copy</button>
      </span>
    </div>
  `;
}

export interface SignatureBlockOpts {
  accent?: 'gold' | 'green' | 'amber' | 'red';
  children: string;
}

/// Container for the type-specific signature panel. `children` is HTML
/// the caller has already produced (and escaped where needed).
export function renderSignatureBlock(opts: SignatureBlockOpts): string {
  const accent = opts.accent ?? 'gold';
  return `
    <div class="sig-block sig-block--${accent}">${opts.children}</div>
  `;
}

/// Wire reveal-toggle + copy click handlers within `scope`. Idempotent —
/// safe to call multiple times against the same scope.
export function wireFieldHandlers(scope: HTMLElement): void {
  scope.querySelectorAll<HTMLButtonElement>('[data-field-action="reveal"]').forEach((btn) => {
    btn.addEventListener('click', () => {
      const row = btn.closest('[data-field-id]') as HTMLElement | null;
      if (!row) return;
      const valueEl = row.querySelector('[data-field-role="value"]') as HTMLElement | null;
      if (!valueEl) return;
      const revealed = row.getAttribute('data-revealed') === 'true';
      const plaintext = row.getAttribute('data-field-value') ?? '';
      const multiline = row.getAttribute('data-field-multiline') === 'true';
      if (revealed) {
        const placeholder = multiline ? `•••• (${plaintext.length} chars)` : '••••';
        valueEl.textContent = placeholder;
        row.setAttribute('data-revealed', 'false');
        btn.textContent = 'show';
      } else {
        const isPassword = row.getAttribute('data-field-kind') === 'password';
        valueEl.textContent = '';
        if (isPassword) {
          valueEl.appendChild(colorizePassword(plaintext));
        } else {
          valueEl.textContent = plaintext;
        }
        row.setAttribute('data-revealed', 'true');
        btn.textContent = 'hide';
      }
    });
  });

  scope.querySelectorAll<HTMLButtonElement>('[data-field-action="copy"]').forEach((btn) => {
    btn.addEventListener('click', async () => {
      const value = btn.getAttribute('data-field-value') ?? '';
      try { await navigator.clipboard.writeText(value); } catch { /* swallow — UX is the visual flash below */ }
      const original = btn.textContent;
      btn.textContent = 'copied';
      setTimeout(() => { if (btn.textContent === 'copied') btn.textContent = original; }, 1500);
    });
  });
}

/// Render an Item's sections as read-only field rows. Each section with
/// ≥1 field emits a header (if named) or thin separator (if anonymous)
/// plus field rows via renderRow / renderConcealedRow. Sections with
/// 0 fields are skipped. Fields with unsupported kinds are silently
/// skipped (β₂ supports text, password, concealed only).
///
/// `idPrefix` uniquifies concealed-row IDs (`${idPrefix}-s{i}-f{j}`)
/// so multiple typed-item detail views rendered in sequence don't
/// collide on wireFieldHandlers lookups.
export function renderSections(item: Item, idPrefix: string): string {
  let out = '';
  item.sections.forEach((section, sIdx) => {
    const visibleFields = section.fields.filter(
      (f) => f.value.kind === 'text' || f.value.kind === 'password' || f.value.kind === 'concealed',
    );
    if (visibleFields.length === 0) return;

    if (section.name) {
      out += `<div class="section-header">${escapeHtml(section.name)}</div>`;
    } else {
      out += `<hr class="section-separator">`;
    }

    visibleFields.forEach((field, fIdx) => {
      if (field.value.kind === 'text') {
        out += renderRow({ label: field.label, value: field.value.value, copyable: true });
      } else if (field.value.kind === 'password' || field.value.kind === 'concealed') {
        out += renderConcealedRow({
          id: `${idPrefix}-s${sIdx}-f${fIdx}`,
          label: field.label,
          value: field.value.value,
          kind: field.value.kind,
        });
      }
    });
  });
  return out;
}

/// 16-char hex FieldId. crypto.getRandomValues for 8 bytes.
export function generateFieldId(): string {
  const bytes = new Uint8Array(8);
  crypto.getRandomValues(bytes);
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
}

function makeField(kind: 'text' | 'password' | 'concealed'): Field {
  const value: FieldValue = { kind, value: '' };
  return {
    id: generateFieldId(),
    label: 'new field',
    kind,
    value,
    hidden_by_default: kind !== 'text',
  };
}

/// Render the collapsible custom-sections editor. Returns HTML for the
/// disclosure toggle + body. The expanded-state is owned externally
/// (via a module-scope flag in the caller); this helper reads it as
/// the `expanded` parameter.
export function renderSectionsEditor(sections: Section[], expanded: boolean): string {
  const sectionCount = sections.length;
  const fieldCount = sections.reduce((sum, s) => sum + s.fields.length, 0);
  const sectionLabel = sectionCount === 1 ? '1 section' : `${sectionCount} sections`;
  const fieldLabel = fieldCount === 1 ? '1 field' : `${fieldCount} fields`;
  const summary = sectionCount === 0 && fieldCount === 0
    ? 'no custom fields'
    : `${sectionLabel}, ${fieldLabel}`;

  const body = sections.map((section, sIdx) => renderSectionBlock(section, sIdx)).join('');

  return `
    <div class="disclosure" data-expanded="${expanded ? 'true' : 'false'}">
      <button type="button" class="disclosure__toggle">▾ custom sections & fields (${escapeHtml(summary)})</button>
      <div class="disclosure__body">
        ${body}
        <button type="button" class="add-section">+ add section</button>
      </div>
    </div>
  `;
}

function renderSectionBlock(section: Section, sIdx: number): string {
  const nameDisplay = section.name
    ? `<span class="name">${escapeHtml(section.name)}</span>`
    : `<span class="name anon">(anonymous)</span>`;

  // Only render supported kinds. Other-kind fields stay in sectionsDraft
  // untouched so they survive save intact.
  const editable = section.fields.filter(
    (f) => f.value.kind === 'text' || f.value.kind === 'password' || f.value.kind === 'concealed',
  );
  const fieldsHtml = editable.map((f) => renderEditorField(f, sIdx, 0)).join('');

  const preservedCount = section.fields.length - editable.length;
  const preservedNote = preservedCount > 0
    ? `<div class="section-editor__preserved">${preservedCount} field${preservedCount === 1 ? '' : 's'} of unsupported kind (edit via CLI)</div>`
    : '';

  return `
    <div class="section-editor" data-section-idx="${sIdx}">
      <div class="section-editor__head">
        ${nameDisplay}
        <span class="actions">
          <button type="button" data-rename-section="${sIdx}">rename</button>
          <button type="button" data-remove-section="${sIdx}">× remove section</button>
        </span>
      </div>
      ${fieldsHtml}
      ${preservedNote}
      <div class="section-editor__add">
        <button type="button" data-add-field="text" data-section-idx="${sIdx}">+ text</button>
        <button type="button" data-add-field="password" data-section-idx="${sIdx}">+ password</button>
        <button type="button" data-add-field="concealed" data-section-idx="${sIdx}">+ concealed</button>
      </div>
    </div>
  `;
}

function renderEditorField(field: Field, sIdx: number, _fIdx: number): string {
  const valueStr = (field.value.kind === 'text' || field.value.kind === 'password' || field.value.kind === 'concealed')
    ? field.value.value
    : '';
  const inputType = field.value.kind === 'text' ? 'text' : 'password';
  return `
    <div class="section-editor__field">
      <input type="text" data-field-label="${escapeHtml(field.id)}" value="${escapeHtml(field.label)}" placeholder="label">
      <input type="${inputType}" data-field-value-input="${escapeHtml(field.id)}" value="${escapeHtml(valueStr)}" placeholder="value">
      <button type="button" class="delete-field" data-delete-field="${escapeHtml(field.id)}" data-section-idx="${sIdx}">×</button>
    </div>
  `;
}

function findField(
  sectionsDraft: Section[],
  fieldId: string,
): { section: Section; fieldIdx: number } | null {
  for (const section of sectionsDraft) {
    const idx = section.fields.findIndex((f) => f.id === fieldId);
    if (idx >= 0) return { section, fieldIdx: idx };
  }
  return null;
}

/// Wire click + input handlers on a rendered sections-editor. Mutations
/// happen in place on `sectionsDraft`. `rerender` is called after any
/// structural change (add/remove) to regenerate the disclosure body;
/// label/value edits do NOT trigger rerender (would steal focus).
export function wireSectionsEditor(
  scope: HTMLElement,
  sectionsDraft: Section[],
  rerender: () => void,
): void {
  const toggle = scope.querySelector('.disclosure__toggle') as HTMLButtonElement | null;
  toggle?.addEventListener('click', () => {
    const disclosure = scope.querySelector('.disclosure') as HTMLElement | null;
    if (!disclosure) return;
    const expanded = disclosure.getAttribute('data-expanded') === 'true';
    disclosure.setAttribute('data-expanded', expanded ? 'false' : 'true');
  });

  scope.querySelector('.add-section')?.addEventListener('click', () => {
    sectionsDraft.push({ name: undefined, fields: [] });
    rerender();
  });

  scope.querySelectorAll<HTMLButtonElement>('[data-rename-section]').forEach((btn) => {
    btn.addEventListener('click', () => {
      const sIdx = Number(btn.dataset.renameSection);
      const current = sectionsDraft[sIdx]?.name ?? '';
      const name = window.prompt('Section name (empty for none):', current);
      if (name === null) return;
      const trimmed = name.trim();
      sectionsDraft[sIdx].name = trimmed || undefined;
      rerender();
    });
  });

  scope.querySelectorAll<HTMLButtonElement>('[data-remove-section]').forEach((btn) => {
    btn.addEventListener('click', () => {
      const sIdx = Number(btn.dataset.removeSection);
      const name = sectionsDraft[sIdx]?.name ?? '(anonymous)';
      if (!window.confirm(`Remove section "${name}" and all its fields?`)) return;
      sectionsDraft.splice(sIdx, 1);
      rerender();
    });
  });

  scope.querySelectorAll<HTMLButtonElement>('[data-add-field]').forEach((btn) => {
    btn.addEventListener('click', () => {
      const sIdx = Number(btn.dataset.sectionIdx);
      const kind = btn.dataset.addField as 'text' | 'password' | 'concealed';
      sectionsDraft[sIdx].fields.push(makeField(kind));
      rerender();
    });
  });

  scope.querySelectorAll<HTMLButtonElement>('[data-delete-field]').forEach((btn) => {
    btn.addEventListener('click', () => {
      const fieldId = btn.dataset.deleteField ?? '';
      const found = findField(sectionsDraft, fieldId);
      if (!found) return;
      found.section.fields = found.section.fields.filter((f) => f.id !== fieldId);
      rerender();
    });
  });

  scope.querySelectorAll<HTMLInputElement>('[data-field-label]').forEach((input) => {
    input.addEventListener('input', () => {
      const fieldId = input.dataset.fieldLabel ?? '';
      const found = findField(sectionsDraft, fieldId);
      if (found) {
        found.section.fields[found.fieldIdx].label = input.value;
      }
    });
  });

  scope.querySelectorAll<HTMLInputElement>('[data-field-value-input]').forEach((input) => {
    input.addEventListener('input', () => {
      const fieldId = input.dataset.fieldValueInput ?? '';
      const found = findField(sectionsDraft, fieldId);
      if (!found) return;
      const field = found.section.fields[found.fieldIdx];
      // Only mutate supported kinds. Unsupported kinds are never rendered
      // as editable (filtered by renderSectionBlock), so this path shouldn't
      // fire for them — but guard defensively.
      if (field.value.kind === 'text' || field.value.kind === 'password' || field.value.kind === 'concealed') {
        const kind = field.value.kind;
        field.value = { kind, value: input.value };
      }
    });
  });
}
