import { expect, fixture, html, elementUpdated } from '@open-wc/testing';
import './nile-rte-link';
function createEditor(content = '<p>hello world</p>') {
    const el = document.createElement('article');
    el.setAttribute('contenteditable', 'true');
    el.innerHTML = content;
    document.body.appendChild(el);
    return el;
}
function selectText(node, start, end) {
    const sel = document.getSelection();
    sel.removeAllRanges();
    const range = document.createRange();
    const textNode = node.firstChild;
    range.setStart(textNode, start);
    range.setEnd(textNode, end);
    sel.addRange(range);
    return range;
}
function collapseAt(node, offset) {
    const sel = document.getSelection();
    sel.removeAllRanges();
    const range = document.createRange();
    const textNode = node.firstChild;
    range.setStart(textNode, offset);
    range.collapse(true);
    sel.addRange(range);
    return range;
}
function sleep(ms) {
    return new Promise(r => setTimeout(r, ms));
}
describe('NileRteLink', () => {
    let editor;
    afterEach(() => {
        editor?.remove();
    });
    // ── Rendering and Defaults ──
    it('1. renders the component', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        expect(el).to.exist;
    });
    it('2. tag name is nile-rte-link', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        expect(el.tagName.toLowerCase()).to.equal('nile-rte-link');
    });
    it('3. default newTab is false', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        expect(el.newTab).to.be.false;
    });
    it('4. default placeholder is "Type or paste link here"', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        expect(el.placeholder).to.equal('Type or paste link here');
    });
    it('5. default label is "Link"', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        expect(el.label).to.equal('Link');
    });
    it('6. renders a link icon button', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const btn = el.querySelector('nile-button');
        expect(btn).to.exist;
        const icon = el.querySelector('nile-icon');
        expect(icon).to.exist;
        expect(icon.getAttribute('name')).to.equal('link_2');
    });
    it('7. renders a popover element', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const pop = el.querySelector('nile-popover');
        expect(pop).to.exist;
    });
    it('8. custom element is defined in registry', async () => {
        expect(customElements.get('nile-rte-link')).to.exist;
    });
    // ── Property Binding ──
    it('9. set newTab to true', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor} newTab></nile-rte-link>`);
        expect(el.newTab).to.be.true;
    });
    it('10. set custom placeholder', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor} placeholder="Enter URL"></nile-rte-link>`);
        expect(el.placeholder).to.equal('Enter URL');
    });
    it('11. set custom label', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor} label="Add Link"></nile-rte-link>`);
        expect(el.label).to.equal('Add Link');
    });
    it('12. set editorEl reference', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        expect(el.editorEl).to.equal(editor);
    });
    // ── URL Normalization ──
    it('13. plain domain gets https:// prepended', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const normalize = el.normalizeUrl.bind(el);
        expect(normalize('example.com')).to.equal('https://example.com');
    });
    it('14. http:// URL stays unchanged', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const normalize = el.normalizeUrl.bind(el);
        expect(normalize('http://example.com')).to.equal('http://example.com');
    });
    it('15. https:// URL stays unchanged', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const normalize = el.normalizeUrl.bind(el);
        expect(normalize('https://example.com')).to.equal('https://example.com');
    });
    it('16. mailto: URL stays unchanged', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const normalize = el.normalizeUrl.bind(el);
        expect(normalize('mailto:a@b.com')).to.equal('mailto:a@b.com');
    });
    it('17. tel: URL stays unchanged', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const normalize = el.normalizeUrl.bind(el);
        expect(normalize('tel:+123')).to.equal('tel:+123');
    });
    it('18. displayUrl returns URL as-is', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const display = el.displayUrl.bind(el);
        expect(display('https://example.com')).to.equal('https://example.com');
        expect(display('http://example.com/path')).to.equal('http://example.com/path');
    });
    // ── Link Insertion (collapsed selection) ──
    it('19. inserts a new <a> tag with URL as text when selection is collapsed', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 4);
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'https://example.com';
        el.applyLink();
        const a = editor.querySelector('a');
        expect(a).to.exist;
        expect(a.href).to.include('example.com');
        expect(a.textContent).to.include('example.com');
    });
    it('20. inserts link with target and rel when newTab is true', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor} newTab></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 4);
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'https://example.com';
        el.applyLink();
        const a = editor.querySelector('a');
        expect(a).to.exist;
        expect(a.target).to.equal('_blank');
        expect(a.rel).to.equal('noopener noreferrer');
    });
    it('21. does not set target/rel when newTab is false', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 4);
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'https://example.com';
        el.applyLink();
        const a = editor.querySelector('a');
        expect(a).to.exist;
        expect(a.hasAttribute('target')).to.be.false;
        expect(a.hasAttribute('rel')).to.be.false;
    });
    // ── Link Insertion (text selected) ──
    it('22. wraps selected text in an <a> tag', async () => {
        editor = createEditor('<p>hello world</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        selectText(p, 0, 5);
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'https://example.com';
        el.applyLink();
        const a = editor.querySelector('a');
        expect(a).to.exist;
        expect(a.href).to.include('example.com');
    });
    it('23. preserves selected text content after wrapping', async () => {
        editor = createEditor('<p>hello world</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        selectText(p, 0, 5);
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'https://example.com';
        el.applyLink();
        const a = editor.querySelector('a');
        expect(a.textContent).to.equal('hello');
    });
    // ── Link Editing ──
    it('24. detects existing anchor and shows edit/unlink buttons', async () => {
        editor = createEditor('<p><a href="https://example.com">link text</a></p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const a = editor.querySelector('a');
        collapseAt(a, 2);
        el.onOpen();
        await elementUpdated(el);
        expect(el.hasActiveLink).to.be.true;
        expect(el.activeAnchor).to.equal(a);
    });
    it('25. pre-fills input with existing link URL', async () => {
        editor = createEditor('<p><a href="https://example.com">link</a></p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const a = editor.querySelector('a');
        collapseAt(a, 2);
        el.onOpen();
        await elementUpdated(el);
        expect(el.linkValue).to.equal(a.href);
    });
    it('26. updates existing anchor href on apply', async () => {
        editor = createEditor('<p><a href="https://old.com">link</a></p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const a = editor.querySelector('a');
        collapseAt(a, 2);
        el.onOpen();
        await elementUpdated(el);
        el.linkValue = 'https://new.com';
        el.applyLink();
        expect(a.href).to.include('new.com');
    });
    it('27. toggling newTab on edit updates target/rel attributes', async () => {
        editor = createEditor('<p><a href="https://example.com">link</a></p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor} newTab></nile-rte-link>`);
        const a = editor.querySelector('a');
        collapseAt(a, 2);
        el.onOpen();
        await elementUpdated(el);
        el.linkValue = 'https://example.com';
        el.applyLink();
        expect(a.target).to.equal('_blank');
        expect(a.rel).to.equal('noopener noreferrer');
    });
    // ── Link Removal ──
    it('28. unlink removes the <a> tag', async () => {
        editor = createEditor('<p><a href="https://example.com">link text</a></p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const a = editor.querySelector('a');
        collapseAt(a, 2);
        el.onOpen();
        await elementUpdated(el);
        el.onRemove();
        expect(editor.querySelector('a')).to.be.null;
    });
    it('29. unlink preserves the text content', async () => {
        editor = createEditor('<p><a href="https://example.com">link text</a></p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const a = editor.querySelector('a');
        collapseAt(a, 2);
        el.onOpen();
        await elementUpdated(el);
        el.onRemove();
        expect(editor.querySelector('p').textContent).to.equal('link text');
    });
    it('30. closes popover after unlink', async () => {
        editor = createEditor('<p><a href="https://example.com">link text</a></p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const a = editor.querySelector('a');
        collapseAt(a, 2);
        el.onOpen();
        await elementUpdated(el);
        el.onRemove();
        expect(el.selectionRange).to.be.null;
        expect(el.hasActiveLink).to.be.false;
    });
    // ── Popover Behavior ──
    it('31. opens popover on button click', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 2);
        el.onOpen();
        await sleep(50);
        const pop = el.querySelector('nile-popover');
        expect(pop?.isShow).to.be.true;
    });
    it('32. closes popover on Escape key', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 2);
        el.onOpen();
        await sleep(50);
        const event = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true });
        el.onInputKeydown(event);
        expect(el.selectionRange).to.be.null;
    });
    it('33. applies link on Enter key', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 4);
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'https://example.com';
        const event = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true });
        el.onInputKeydown(event);
        const a = editor.querySelector('a');
        expect(a).to.exist;
    });
    it('34. closes popover on input blur', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 2);
        el.onOpen();
        await sleep(50);
        el.ignoreBlur = false;
        el.isApplying = false;
        el.onInputBlur();
        await sleep(50);
        expect(el.selectionRange).to.be.null;
    });
    it('35. does not close on blur when ignoreBlur is set', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 2);
        el.onOpen();
        await sleep(50);
        el.ignoreBlur = true;
        el.onInputBlur();
        expect(el.selectionRange).to.not.be.null;
    });
    // ── Event Dispatching ──
    it('36. fires nile-link-changed with href on link apply', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 4);
        let detail = null;
        el.addEventListener('nile-link-changed', ((e) => {
            detail = e.detail;
        }));
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'https://example.com';
        el.applyLink();
        expect(detail).to.exist;
        expect(detail.href).to.include('example.com');
    });
    it('37. fires nile-link-changed with empty href on unlink', async () => {
        editor = createEditor('<p><a href="https://example.com">link</a></p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const a = editor.querySelector('a');
        collapseAt(a, 2);
        let detail = null;
        el.addEventListener('nile-link-changed', ((e) => {
            detail = e.detail;
        }));
        el.onOpen();
        await elementUpdated(el);
        el.onRemove();
        expect(detail).to.exist;
        expect(detail.href).to.equal('');
    });
    it('38. event bubbles and is composed', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 4);
        let bubbles = false;
        let composed = false;
        el.addEventListener('nile-link-changed', ((e) => {
            bubbles = e.bubbles;
            composed = e.composed;
        }));
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'https://example.com';
        el.applyLink();
        expect(bubbles).to.be.true;
        expect(composed).to.be.true;
    });
    // ── Validation ──
    it('39. does not apply empty URL', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 4);
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = '';
        el.applyLink();
        expect(editor.querySelector('a')).to.be.null;
    });
    it('40. rejects unsupported protocols', async () => {
        editor = createEditor('<p>text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        collapseAt(p, 4);
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'javascript:alert(1)';
        el.applyLink();
        expect(editor.querySelector('a')).to.be.null;
    });
    // ── Integration with Parent RTE ──
    it('41. parent receives nile-link-changed event and updates content', async () => {
        editor = createEditor('<p>text</p>');
        const container = await fixture(html `
      <div>
        <nile-rte-link .editorEl=${editor}></nile-rte-link>
      </div>
    `);
        const el = container.querySelector('nile-rte-link');
        let received = false;
        container.addEventListener('nile-link-changed', () => {
            received = true;
        });
        const p = editor.querySelector('p');
        collapseAt(p, 4);
        el.selectionRange = document.getSelection().getRangeAt(0).cloneRange();
        el.linkValue = 'https://example.com';
        el.applyLink();
        expect(received).to.be.true;
    });
    it('42. toolbar link button shows active state when cursor is inside a link', async () => {
        editor = createEditor('<p><a href="https://example.com">link text</a></p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const a = editor.querySelector('a');
        collapseAt(a, 2);
        el.onOpen();
        await elementUpdated(el);
        expect(el.hasActiveLink).to.be.true;
    });
    it('43. toolbar link button loses active state when cursor moves outside a link', async () => {
        editor = createEditor('<p>normal <a href="https://example.com">link</a> text</p>');
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const p = editor.querySelector('p');
        const textNode = p.firstChild;
        const sel = document.getSelection();
        sel.removeAllRanges();
        const range = document.createRange();
        range.setStart(textNode, 2);
        range.collapse(true);
        sel.addRange(range);
        el.onOpen();
        await elementUpdated(el);
        expect(el.hasActiveLink).to.be.false;
        expect(el.activeAnchor).to.be.null;
    });
    // ── CSS Injection ──
    it('44. injects style tag with data-rte-link-style attribute', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const styleTag = el.querySelector('style[data-rte-link-style]');
        expect(styleTag).to.exist;
    });
    it('45. does not inject duplicate styles on reconnect', async () => {
        editor = createEditor();
        const el = await fixture(html `<nile-rte-link .editorEl=${editor}></nile-rte-link>`);
        const parent = el.parentElement;
        parent.removeChild(el);
        parent.appendChild(el);
        await elementUpdated(el);
        const styleTags = el.querySelectorAll('style[data-rte-link-style]');
        expect(styleTags.length).to.equal(1);
    });
});
//# sourceMappingURL=nile-rte-link.test.js.map