import { MockedD3 } from './tests/MockedD3.js';
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
import type { D3Element } from './types.js';

describe('accessibility', () => {
  const fauxSvgNode: MockedD3 = new MockedD3();

  describe('setA11yDiagramInfo', () => {
    it('should set svg element role to "graphics-document document"', () => {
      const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
      setA11yDiagramInfo(fauxSvgNode, 'flowchart');
      expect(svgAttrSpy).toHaveBeenCalledWith('role', 'graphics-document document');
    });

    it('should set aria-roledescription to the diagram type', () => {
      const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
      setA11yDiagramInfo(fauxSvgNode, 'flowchart');
      expect(svgAttrSpy).toHaveBeenCalledWith('aria-roledescription', 'flowchart');
    });

    it('should not set aria-roledescription if the diagram type is empty', () => {
      const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
      setA11yDiagramInfo(fauxSvgNode, '');
      expect(svgAttrSpy).toHaveBeenCalledTimes(1);
      expect(svgAttrSpy).toHaveBeenCalledWith('role', expect.anything()); // only called to set the role
    });
  });

  describe('addSVGa11yTitleDescription', () => {
    const givenId = 'theBaseId';

    describe('with svg d3 object', () => {
      it('should do nothing if there is no insert defined', () => {
        const noInsertSvg = {
          attr: vi.fn(),
        };
        const noInsertAttrSpy = vi.spyOn(noInsertSvg, 'attr').mockReturnValue(noInsertSvg);
        addSVGa11yTitleDescription(noInsertSvg, 'some title', 'some desc', givenId);
        expect(noInsertAttrSpy).not.toHaveBeenCalled();
      });

      // convenience functions to DRY up the spec

      function expectAriaLabelledByItTitleId(
        svgD3Node: D3Element,
        title: string | undefined,
        desc: string | undefined,
        givenId: string
      ): void {
        const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
        addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
        expect(svgAttrSpy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`);
      }

      function expectAriaDescribedByItDescId(
        svgD3Node: D3Element,
        title: string | undefined,
        desc: string | undefined,
        givenId: string
      ): void {
        const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
        addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
        expect(svgAttrSpy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`);
      }

      function a11yTitleTagInserted(
        svgD3Node: D3Element,
        title: string | undefined,
        desc: string | undefined,
        givenId: string,
        callNumber: number
      ): void {
        a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title);
      }

      function a11yDescTagInserted(
        svgD3Node: D3Element,
        title: string | undefined,
        desc: string | undefined,
        givenId: string,
        callNumber: number
      ): void {
        a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc);
      }

      function a11yTagInserted(
        _svgD3Node: D3Element,
        title: string | undefined,
        desc: string | undefined,
        givenId: string,
        callNumber: number,
        expectedPrefix: string,
        expectedText: string | undefined
      ): void {
        const fauxInsertedD3: MockedD3 = new MockedD3();
        const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3);
        const titleAttrSpy = vi.spyOn(fauxInsertedD3, 'attr').mockReturnValue(fauxInsertedD3);
        const titleTextSpy = vi.spyOn(fauxInsertedD3, 'text');

        addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId);
        expect(svginsertpy).toHaveBeenCalledWith(expectedPrefix, ':first-child');
        expect(titleAttrSpy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`);
        expect(titleTextSpy).toHaveBeenNthCalledWith(callNumber, expectedText);
      }

      describe('with a11y title', () => {
        const a11yTitle = 'a11y title';

        describe('with a11y description', () => {
          const a11yDesc = 'a11y description';

          it('shold set aria-labelledby to the title id inserted as a child', () => {
            expectAriaLabelledByItTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
          });

          it('should set aria-describedby to the description id inserted as a child', () => {
            expectAriaDescribedByItDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
          });

          it('should insert title tag as the first child with the text set to the accTitle given', () => {
            a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2);
          });

          it('should insert desc tag as the 2nd child with the text set to accDescription given', () => {
            a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
          });
        });

        describe(`without a11y description`, () => {
          const a11yDesc = undefined;

          it('should set aria-labelledby to the title id inserted as a child', () => {
            expectAriaLabelledByItTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
          });

          it('should not set aria-describedby', () => {
            const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
            addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
            expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
          });

          it('should insert title tag as the first child with the text set to the accTitle given', () => {
            a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
          });

          it('should not insert description tag', () => {
            const fauxTitle: MockedD3 = new MockedD3();
            const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
            addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
            expect(svginsertpy).not.toHaveBeenCalledWith('desc', ':first-child');
          });
        });
      });

      describe('without a11y title', () => {
        const a11yTitle = undefined;

        describe('with a11y description', () => {
          const a11yDesc = 'a11y description';

          it('should not set aria-labelledby', () => {
            const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
            addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
            expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
          });

          it('should not insert title tag', () => {
            const fauxTitle: MockedD3 = new MockedD3();
            const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
            addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
            expect(svginsertpy).not.toHaveBeenCalledWith('title', ':first-child');
          });

          it('should set aria-describedby to the description id inserted as a child', () => {
            expectAriaDescribedByItDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
          });

          it('should insert desc tag as the 2nd child with the text set to accDescription given', () => {
            a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
          });
        });

        describe('without a11y description', () => {
          const a11yDesc = undefined;

          it('should not set aria-labelledby', () => {
            const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
            addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
            expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
          });

          it('should not set aria-describedby', () => {
            const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
            addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
            expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
          });

          it('should not insert title tag', () => {
            const fauxTitle: MockedD3 = new MockedD3();
            const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
            addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
            expect(svginsertpy).not.toHaveBeenCalledWith('title', ':first-child');
          });

          it('should not insert  description tag', () => {
            const fauxDesc: MockedD3 = new MockedD3();
            const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc);
            addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
            expect(svginsertpy).not.toHaveBeenCalledWith('desc', ':first-child');
          });
        });
      });
    });
  });
});
