import { describe, expect, it, vi } from 'vitest';

vi.mock('../../../shared/state', async () => {
  const escapeHtml = (s: string) => s
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
  return { escapeHtml, popOutToTab: vi.fn(), isInTab: vi.fn(() => false), openVaultTab: vi.fn() };
});

import { renderSectionsEditor, generateFieldId, wireSectionsEditor } from '../fields';
import type { Section } from '../../../shared/types';

describe('generateFieldId', () => {
  it('returns 16 hex chars', () => {
    const id = generateFieldId();
    expect(id).toMatch(/^[0-9a-f]{16}$/);
  });
  it('returns unique values on successive calls', () => {
    const ids = new Set(Array.from({ length: 50 }, () => generateFieldId()));
    expect(ids.size).toBe(50);
  });
});

describe('renderSectionsEditor', () => {
  it('shows the disclosure toggle with the correct count', () => {
    const sections: Section[] = [
      { name: 'a', fields: [
        { id: 'f0', label: 'l', kind: 'text', value: { kind: 'text', value: 'v' }, hidden_by_default: false },
        { id: 'f1', label: 'l', kind: 'password', value: { kind: 'password', value: 'p' }, hidden_by_default: true },
      ] },
      { fields: [
        { id: 'f2', label: 'l', kind: 'concealed', value: { kind: 'concealed', value: 'c' }, hidden_by_default: true },
      ] },
    ];
    const html = renderSectionsEditor(sections, false);
    expect(html).toContain('2 sections');
    expect(html).toContain('3 fields');
    expect(html).toContain('data-expanded="false"');
  });

  it('shows singular "1 section / 1 field" when applicable', () => {
    const sections: Section[] = [
      { name: 'only', fields: [
        { id: 'f0', label: 'l', kind: 'text', value: { kind: 'text', value: 'v' }, hidden_by_default: false },
      ] },
    ];
    const html = renderSectionsEditor(sections, false);
    expect(html).toContain('1 section');
    expect(html).toContain('1 field');
    expect(html).not.toContain('1 sections');
    expect(html).not.toContain('1 fields');
  });

  it('renders expanded body when expanded=true', () => {
    const html = renderSectionsEditor([], true);
    expect(html).toContain('data-expanded="true"');
    expect(html).toContain('add section');
  });
});

describe('wireSectionsEditor', () => {
  it('toggle click flips data-expanded', () => {
    document.body.innerHTML = renderSectionsEditor([], false);
    const sections: Section[] = [];
    const rerender = vi.fn();
    wireSectionsEditor(document.body, sections, rerender);
    const toggle = document.querySelector('.disclosure__toggle') as HTMLButtonElement;
    toggle.click();
    const disclosure = document.querySelector('.disclosure') as HTMLElement;
    expect(disclosure.getAttribute('data-expanded')).toBe('true');
  });

  it('add-section click appends an empty section', () => {
    const sections: Section[] = [];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    const rerender = vi.fn();
    wireSectionsEditor(document.body, sections, rerender);
    const addBtn = document.querySelector('.add-section') as HTMLButtonElement;
    addBtn.click();
    expect(sections).toHaveLength(1);
    expect(sections[0]).toEqual({ name: undefined, fields: [] });
    expect(rerender).toHaveBeenCalled();
  });

  it('add-text-field click on a section pushes a text field', () => {
    const sections: Section[] = [{ name: undefined, fields: [] }];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    const rerender = vi.fn();
    wireSectionsEditor(document.body, sections, rerender);
    const addText = document.querySelector('[data-add-field="text"][data-section-idx="0"]') as HTMLButtonElement;
    addText.click();
    expect(sections[0].fields).toHaveLength(1);
    expect(sections[0].fields[0].kind).toBe('text');
    expect(sections[0].fields[0].value.kind).toBe('text');
    expect(sections[0].fields[0].value.value).toBe('');
    expect(sections[0].fields[0].hidden_by_default).toBe(false);
    expect(sections[0].fields[0].id).toMatch(/^[0-9a-f]{16}$/);
  });

  it('add-password-field sets hidden_by_default=true', () => {
    const sections: Section[] = [{ name: undefined, fields: [] }];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    wireSectionsEditor(document.body, sections, vi.fn());
    (document.querySelector('[data-add-field="password"][data-section-idx="0"]') as HTMLButtonElement).click();
    expect(sections[0].fields[0].hidden_by_default).toBe(true);
    expect(sections[0].fields[0].kind).toBe('password');
  });

  it('remove-field button splices field', () => {
    const sections: Section[] = [{ name: undefined, fields: [
      { id: 'f0', label: 'a', kind: 'text', value: { kind: 'text', value: '' }, hidden_by_default: false },
      { id: 'f1', label: 'b', kind: 'text', value: { kind: 'text', value: '' }, hidden_by_default: false },
    ] }];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    wireSectionsEditor(document.body, sections, vi.fn());
    const deleteBtn = document.querySelector('[data-delete-field="f0"]') as HTMLButtonElement;
    deleteBtn.click();
    expect(sections[0].fields).toHaveLength(1);
    expect(sections[0].fields[0].id).toBe('f1');
  });

  it('remove-section button splices section (after confirm)', () => {
    const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
    const sections: Section[] = [
      { name: 'to-remove', fields: [] },
      { name: 'keep', fields: [] },
    ];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    wireSectionsEditor(document.body, sections, vi.fn());
    (document.querySelector('[data-remove-section="0"]') as HTMLButtonElement).click();
    expect(sections).toHaveLength(1);
    expect(sections[0].name).toBe('keep');
    confirmSpy.mockRestore();
  });

  it('remove-section cancelled confirm leaves section intact', () => {
    const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false);
    const sections: Section[] = [{ name: 'stays', fields: [] }];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    wireSectionsEditor(document.body, sections, vi.fn());
    (document.querySelector('[data-remove-section="0"]') as HTMLButtonElement).click();
    expect(sections).toHaveLength(1);
    confirmSpy.mockRestore();
  });

  it('label input change mutates section field label in place (no rerender)', () => {
    const sections: Section[] = [{ name: undefined, fields: [
      { id: 'f0', label: 'old', kind: 'text', value: { kind: 'text', value: '' }, hidden_by_default: false },
    ] }];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    const rerender = vi.fn();
    wireSectionsEditor(document.body, sections, rerender);
    const labelInput = document.querySelector('[data-field-label="f0"]') as HTMLInputElement;
    labelInput.value = 'new';
    labelInput.dispatchEvent(new Event('input', { bubbles: true }));
    expect(sections[0].fields[0].label).toBe('new');
    expect(rerender).not.toHaveBeenCalled();
  });

  it('value input change mutates section field value in place', () => {
    const sections: Section[] = [{ name: undefined, fields: [
      { id: 'f0', label: 'l', kind: 'text', value: { kind: 'text', value: 'old' }, hidden_by_default: false },
    ] }];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    wireSectionsEditor(document.body, sections, vi.fn());
    const valueInput = document.querySelector('[data-field-value-input="f0"]') as HTMLInputElement;
    valueInput.value = 'new';
    valueInput.dispatchEvent(new Event('input', { bubbles: true }));
    expect(sections[0].fields[0].value).toEqual({ kind: 'text', value: 'new' });
  });
});

describe('wireSectionsEditor preserves unsupported-kind fields on save', () => {
  it('renders preserved note when section contains unsupported-kind fields', () => {
    const sections: Section[] = [{
      name: 'mixed',
      fields: [
        { id: 'f0000001', label: 'note', kind: 'text',
          value: { kind: 'text', value: 'ok' }, hidden_by_default: false },
        { id: 'f0000002', label: 'when', kind: 'date' as any,
          value: { kind: 'date', value: '2026-01-01' } as any, hidden_by_default: false },
      ],
    }];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    expect(document.body.innerHTML).toContain('1 field of unsupported kind');
    expect(document.body.innerHTML).not.toContain('f0000002');
  });

  it('add-text then save does not destroy unsupported-kind fields', () => {
    const sections: Section[] = [{
      name: 'mixed',
      fields: [
        { id: 'f0000002', label: 'when', kind: 'date' as any,
          value: { kind: 'date', value: '2026-01-01' } as any, hidden_by_default: false },
      ],
    }];
    document.body.innerHTML = renderSectionsEditor(sections, true);
    wireSectionsEditor(document.body, sections, vi.fn());
    const addText = document.querySelector('[data-add-field="text"][data-section-idx="0"]') as HTMLButtonElement;
    addText.click();
    expect(sections[0].fields).toHaveLength(2);
    // Unsupported-kind field preserved untouched.
    const dateField = sections[0].fields.find((f) => f.id === 'f0000002');
    expect(dateField).toBeDefined();
    expect(dateField!.value).toEqual({ kind: 'date', value: '2026-01-01' });
  });
});
