import {BrowserController} from '#testing/helpers/service';
import {poll as classicPoll} from '#testing/iframe';

const TIMEOUT = 2000;

describes.sandboxed('amp-bind', {}, function () {
  this.timeout(TIMEOUT);

  // Helper that sets the poll timeout.
  function poll(desc, condition, onError) {
    return classicPoll(desc, condition, onError, 800);
  }

  describes.integration(
    'basic',
    {
      body: `
      <button on="tap:AMP.setState({t: 'after_text'})" id="changeText"></button>
      <button on="tap:AMP.setState({c: 'after_class'})" id="changeClass"></button>
      <p class="before_class" [class]="c" [text]="t">before_text</p>
    `,
      extensions: ['amp-bind'],
    },
    (env) => {
      let browser;
      let doc;
      let text;

      beforeEach(() => {
        doc = env.win.document;
        text = doc.querySelector('p');
        browser = new BrowserController(env.win);
      });

      it('[text]', async () => {
        expect(text.textContent).to.equal('before_text');
        await browser.wait(200);
        browser.click('#changeText');
        await poll('[text]', () => text.textContent === 'after_text').should.be
          .fulfilled;
      });

      it('[class]', async () => {
        expect(text.className).to.equal('before_class');
        await browser.wait(200);
        browser.click('#changeClass');
        await poll('[class]', () => text.className === 'after_class').should.be
          .fulfilled;
      });
    }
  );

  const BEFORE_IMG_DATA_URL =
    'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAABGdBTUEAALGPC/xhBQAAABtJREFUWAntwYEAAAAAw6D7Uw/hAtUAAAAAjgAQQAABWWUZmwAAAABJRU5ErkJggg==';
  const AFTER_IMG_DATA_URL =
    'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAABGdBTUEAALGPC/xhBQAAAClJREFUWMPtzEERAAAMAiD7l9YQ++0gAOlRBAKBQCAQCAQCgUAgEHwPBqcL8OI+wmxEAAAAAElFTkSuQmCC';
  describes.integration(
    '+ amp-img',
    {
      body: `
      <amp-img id="image" layout="responsive"
        src="${BEFORE_IMG_DATA_URL}" [src]="src"
        alt="before_alt" [alt]="alt"
        width="1" [width]="w"
        height="1" [height]="h"></amp-img>

      <button on="tap:AMP.setState({src: '${AFTER_IMG_DATA_URL}'})" id="changeSrc"></button>
      <button on="tap:AMP.setState({alt: 'after_alt'})" id="changeAlt"></button>
      <button on="tap:AMP.setState({w: 2, h: 2})" id="changeSize"></button>
    `,
      extensions: ['amp-bind'],
    },
    (env) => {
      let doc, img;

      beforeEach(() => {
        doc = env.win.document;
        img = doc.querySelector('amp-img');
      });

      it('[src] with valid URL', async () => {
        const button = doc.getElementById('changeSrc');
        expect(img.getAttribute('src')).to.equal(BEFORE_IMG_DATA_URL);
        button.click();
        await poll(
          '[src]',
          () => img.getAttribute('src') === AFTER_IMG_DATA_URL
        ).should.be.fulfilled;
      });

      it('[alt]', async () => {
        const button = doc.getElementById('changeAlt');
        expect(img.getAttribute('alt')).to.equal('before_alt');
        button.click();
        await poll('[src]', () => img.getAttribute('alt') === 'after_alt')
          .should.be.fulfilled;
      });

      it('[width] and [height]', async () => {
        const button = doc.getElementById('changeSize');
        expect(img.getAttribute('width')).to.equal('1');
        expect(img.getAttribute('height')).to.equal('1');
        button.click();
        await Promise.all([
          poll('[width]', () => img.getAttribute('width') === '2'),
          poll('[height]', () => img.getAttribute('height') === '2'),
        ]).should.be.fulfilled;
      });
    }
  );

  describes.integration(
    '+ forms',
    {
      body: `
      <input type="range" min=0 max=100 value=0 on="change:AMP.setState({rangeChange: event})">
      <p id="range" [text]="rangeChange.min + ' <= ' + rangeChange.value + ' <= ' + rangeChange.max">before_range</p>

      <input type="checkbox" on="change:AMP.setState({checkboxChange: event})" [checked]="isChecked">
      <p id="checkbox" [text]="'checked: ' + checkboxChange.checked" id="checkboxText">before_check</p>
      <button on="tap:AMP.setState({isChecked: isChecked != null ? !isChecked : false})"></button>

      <input type="radio" on="change:AMP.setState({radioChange: event})">
      <p id="radio" [text]="'checked: ' + radioChange.checked" id="radioText">before_radio</p>
    `,
      extensions: ['amp-bind'],
    },
    (env) => {
      let doc;

      beforeEach(() => {
        doc = env.win.document;
      });

      it('input[type=range] on:change', async () => {
        const rangeText = doc.getElementById('range');
        const range = doc.querySelector('input[type="range"]');
        expect(rangeText.textContent).to.equal('before_range');
        // Calling #click() on the range element will not generate a change event,
        // so it must be generated manually.
        range.value = 47;
        range.dispatchEvent(new Event('change', {bubbles: true}));
        await poll('[text]', () => rangeText.textContent === '0 <= 47 <= 100')
          .should.be.fulfilled;
      });

      it('input[type=checkbox] on:change', async () => {
        const checkboxText = doc.getElementById('checkbox');
        const checkbox = doc.querySelector('input[type="checkbox"]');
        expect(checkboxText.textContent).to.equal('before_check');
        checkbox.click();
        await poll('[text]', () => checkboxText.textContent === 'checked: true')
          .should.be.fulfilled;
      });

      it('[checked]', async () => {
        const checkbox = doc.querySelector('input[type="checkbox"]');
        const button = doc.querySelector('button');

        checkbox.click();
        // Note that attributes are initial values, properties are current values.
        expect(checkbox.hasAttribute('checked')).to.be.false;
        expect(checkbox.checked).to.be.true;

        button.click();
        await poll('[checked]', () => !checkbox.checked).should.be.fulfilled;
        expect(checkbox.hasAttribute('checked')).to.be.false;

        button.click();
        await poll('[checked]', () => checkbox.checked).should.be.fulfilled;
        // amp-bind sets both the attribute and property.
        expect(checkbox.hasAttribute('checked')).to.be.true;
      });

      it('input[type=radio] on:change', async () => {
        const radioText = doc.getElementById('radio');
        const radio = doc.querySelector('input[type="radio"]');
        expect(radioText.textContent).to.equal('before_radio');
        radio.click();
        await poll('[text]', () => radioText.textContent === 'checked: true')
          .should.be.fulfilled;
      });
    }
  );

  describes.integration(
    '+ amp-carousel',
    {
      body: `
      <button on="tap:AMP.setState({slide: 1})" id="goToSlideOne"></button>
      <p [text]="slide">0</p>
      <amp-carousel type="slides" width=10 height=10
          on="slideChange:AMP.setState({slide: event.index})" [slide]="slide">
        <amp-img src="http://example.com/foo.jpg" width=10 height=10></amp-img>
        <amp-img src="http://example.com/bar.jpg" width=10 height=10></amp-img>
      </amp-carousel>
    `,
      extensions: ['amp-bind', 'amp-carousel'],
    },
    (env) => {
      let doc, carousel, slideText;

      beforeEach(async () => {
        doc = env.win.document;
        carousel = doc.querySelector('amp-carousel');
        slideText = doc.querySelector('p');

        const browserController = new BrowserController(env.win);
        await browserController.waitForElementLayout('amp-carousel');
      });

      it('on:slideChange', async () => {
        expect(slideText.textContent).to.equal('0');

        const nextSlide = carousel.querySelector(
          'div.amp-carousel-button-next'
        );
        nextSlide.click();
        await poll('[slide]', () => slideText.textContent === '1').should.be
          .fulfilled;
      });

      it('[slide]', async () => {
        const slides = carousel.querySelectorAll(
          '.i-amphtml-slide-item > amp-img'
        );
        const first = slides[0];
        const second = slides[1];

        expect(first.getAttribute('aria-hidden')).to.equal('false');
        expect(second.getAttribute('aria-hidden')).to.be.equal('true');

        const button = doc.getElementById('goToSlideOne');
        button.click();

        await poll(
          '[slide]',
          () => first.getAttribute('aria-hidden') === 'true'
        ).should.be.fulfilled;
        await poll(
          '[slide]',
          () => second.getAttribute('aria-hidden') === 'false'
        ).should.be.fulfilled;
      });
    }
  );

  const list = `
    <amp-state id="foo">
      <script type="application/json">
        {"bar": "123"}
      </script>
    </amp-state>
    <button on="tap:AMP.setState({src: 'https://example.com/data'})"></button>
    <amp-list width=200 height=50 template=mustache src="/list/fruit-data/get?cors=0" [src]="src">
    </amp-list>
    <template id=mustache type="amp-mustache">
      <p [text]="foo.bar"></p>
    </template>
  `;

  const listTests = (env) => {
    let doc;
    let list;
    let browser;

    beforeEach(() => {
      doc = env.win.document;
      list = doc.querySelector('amp-list');
      browser = new BrowserController(env.win);
    });

    it('[src]', async () => {
      expect(list.getAttribute('src')).to.equal('/list/fruit-data/get?cors=0');
      browser.click('button');
      await poll(
        '[src]',
        () => list.getAttribute('src') === 'https://example.com/data'
      ).should.be.fulfilled;
    });

    it('evaluate bindings in children', async () => {
      await browser.waitForElementLayout('amp-list');
      const children = list.querySelectorAll('p');
      expect(children.length).to.equal(3);
      children.forEach((span) => {
        expect(span.textContent).to.equal('123');
      });
    });
  };

  describes.integration(
    '+ amp-list, amp-mustache:0.1',
    {
      body: list,
      extensions: ['amp-bind', 'amp-list', 'amp-mustache:0.1'],
    },
    listTests
  );

  describes.integration(
    '+ amp-list, amp-mustache:0.2',
    {
      body: list,
      extensions: ['amp-bind', 'amp-list', 'amp-mustache:0.2'],
    },
    listTests
  );

  describes.integration(
    '+ amp-selector',
    {
      body: `
      <button on="tap:AMP.setState({selected: 2})"></button>
      <p [text]="selected"></p>
      <amp-selector layout="container" [selected]="selected" on="select:AMP.setState({selected: event.targetOption})">
        <amp-img src="/0.jpg" width=10 height=10 option=0></amp-img>
        <amp-img src="/1.jpg" width=10 height=10 option=1></amp-img>
        <amp-img src="/2.jpg" width=10 height=10 option=2></amp-img>
      </amp-selector>
    `,
      extensions: ['amp-bind', 'amp-selector'],
    },
    (env) => {
      let doc, images, selectedText;

      beforeEach(async () => {
        doc = env.win.document;
        images = doc.getElementsByTagName('amp-img');
        selectedText = doc.querySelector('p');

        const browserController = new BrowserController(env.win);
        await browserController.waitForElementLayout('amp-selector');
      });

      it('on:select', async () => {
        expect(images[0].hasAttribute('selected')).to.be.false;
        expect(images[1].hasAttribute('selected')).to.be.false;
        expect(images[2].hasAttribute('selected')).to.be.false;
        expect(selectedText.textContent).to.equal('');
        images[1].click();
        await poll('[text]', () => selectedText.textContent === '1').should.be
          .fulfilled;
        expect(images[0].hasAttribute('selected')).to.be.false;
        expect(images[1].hasAttribute('selected')).to.be.true;
        expect(images[2].hasAttribute('selected')).to.be.false;
      });

      it('[selected]', async () => {
        const button = doc.querySelector('button');
        expect(images[0].hasAttribute('selected')).to.be.false;
        expect(images[1].hasAttribute('selected')).to.be.false;
        expect(images[2].hasAttribute('selected')).to.be.false;
        expect(selectedText.textContent).to.equal('');
        button.click();
        await poll('[text]', () => selectedText.textContent === '2').should.be
          .fulfilled;
        expect(images[0].hasAttribute('selected')).to.be.false;
        expect(images[1].hasAttribute('selected')).to.be.false;
        expect(images[2].hasAttribute('selected')).to.be.true;
      });
    }
  );
});
