// Marks this file as an ES module (rather than a global script) so its
// top-level consts don't collide, under the TS type-checker, with the same
// names declared in other spec files — see e.g. download.cy.ts, which gets
// this for free via a real import.
export {}

const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const serverType = Cypress.env('serverType')
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)

// Viewer column-label display toggle (`?labels=true`): clients surfacing DC in
// SAS Visual Analytics want column LABELs shown instead of NAMEs. Default
// behavior (no param) must stay unchanged; the right-click context menu's
// "Show labels"/"Show names" item is the in-app affordance, and it drives the
// same URL param so state is shareable/refreshable. See mock data in
// sas/mocks/sasjs/services/public/viewdata.js: MPE_X_TEST's SOME_CHAR/SOME_DATE
// have LABELs that differ from NAME, and SOME_NUM has a blank LABEL (fallback
// to NAME) — this spec exercises exactly that fixture.
context('viewer column labels toggle tests: ', function () {
  this.beforeAll(() => {
    cy.visit(`${hostUrl}/SASLogon/logout`)
    // forceLicenceKey=true: without it, loginAndUpdateValidKey() only
    // generates key data and logs back out if no valid license already
    // exists — it doesn't actually apply one (see commands.ts). Several
    // specs call it with no argument and rely on an *earlier* spec in the
    // same CI run having already forced a valid license onto the shared
    // server first (csv.cy.ts, excel.cy.ts, etc.). This spec shouldn't
    // depend on run order, so force it explicitly.
    cy.loginAndUpdateValidKey(true)
  })

  this.beforeEach(() => {
    // No re-login here: beforeAll's loginAndUpdateValidKey() already
    // authenticated the session, and it persists across tests in this file
    // (see filtering.cy.ts/csv.cy.ts for the same pattern). Re-typing into
    // the login form would fail — it's hidden once already logged in.
    cy.visit(hostUrl + appLocation)

    // Visit 'home' first, matching filtering.cy.ts/csv.cy.ts/download.cy.ts —
    // going straight to 'view/data' skips whatever startup-data fetch 'home'
    // triggers, which passed locally (fast round-trips) but left the
    // nav-tree empty in CI's slower environment.
    visitPage('home')
    visitPage('view/data')
  })

  it('1 | default (no param): headers show NAMEs', () => {
    openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')

    cy.get('#hotTable .ht_clone_top .htCore thead tr', {
      timeout: longerCommandTimeout
    }).should(($headerRow) => {
      expect($headerRow[0].innerHTML).to.include('SOME_CHAR')
      expect($headerRow[0].innerHTML).to.not.include('Some Character Column')
    })
  })

  it('2 | ?labels=true: headers show LABELs, blank LABEL falls back to NAME', () => {
    openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
    appendUrlParam('labels=true')

    cy.get('#hotTable .ht_clone_top .htCore thead tr', {
      timeout: longerCommandTimeout
    }).should(($headerRow) => {
      const html = $headerRow[0].innerHTML
      expect(html).to.include('Some Character Column')
      expect(html).to.include('Some Date')
      // SOME_NUM has a blank LABEL in the mock -> falls back to NAME
      expect(html).to.include('SOME_NUM')
    })
  })

  it('3 | context menu "Show labels"/"Show names" toggles headers and the URL', () => {
    openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')

    toggleColumnLabelsFromContextMenu('Show labels')

    cy.url().should('include', 'labels=true')
    cy.get('#hotTable .ht_clone_top .htCore thead tr', {
      timeout: longerCommandTimeout
    }).should(($headerRow) => {
      expect($headerRow[0].innerHTML).to.include('Some Character Column')
    })

    toggleColumnLabelsFromContextMenu('Show names')

    cy.url().should('not.include', 'labels=true')
    cy.get('#hotTable .ht_clone_top .htCore thead tr', {
      timeout: longerCommandTimeout
    }).should(($headerRow) => {
      expect($headerRow[0].innerHTML).to.include('SOME_CHAR')
    })
  })

  it('4 | ?embed=va&labels=true: labels shown with embed chrome hidden', () => {
    openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
    appendUrlParam('embed=va&labels=true')

    cy.get('header.app-header').should('not.exist')
    cy.get('#hotTable .ht_clone_top .htCore thead tr', {
      timeout: longerCommandTimeout
    }).should(($headerRow) => {
      expect($headerRow[0].innerHTML).to.include('Some Character Column')
    })
  })

  it('5 | info dropdown: first line is NAME (in both toggle states)', () => {
    openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')

    // No assertion on the item's label text here: `info` has a custom
    // `renderer`, so Handsontable never renders its `name` ("test info") as
    // text at all — the renderer's own output (checked below) is the only
    // real content.
    openColumnDropdown('SOME_CHAR')
    cy.get('.htDropdownMenu').should(($menu) => {
      expect($menu.text()).to.match(/NAME: SOME_CHAR/)
    })
    cy.get('body').click(0, 0) // close menu

    appendUrlParam('labels=true')

    openColumnDropdown('Some Character Column')
    cy.get('.htDropdownMenu').should(($menu) => {
      // NAME is always shown first, even while headers display as LABEL
      expect($menu.text()).to.match(/NAME: SOME_CHAR/)
    })
  })
})

const visitPage = (url: string) => {
  cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}

// Re-visits the current route with extra hash-query params appended, then
// forces a real reload. Under hash routing (`useHash: true`), changing only
// the URL's hash fragment is a same-document navigation — Angular Router
// still reacts to it (which is enough for `useLabels`, read reactively from
// `route.queryParams`), but `embed` is parsed once from `window.location.hash`
// at app bootstrap (app.component.ts), so `?embed=va` only takes effect after
// an actual reload.
const appendUrlParam = (param: string) => {
  cy.url().then((url) => {
    const separator = url.includes('?') ? '&' : '?'
    cy.visit(`${url}${separator}${param}`)
    cy.reload()
    cy.get('.app-loading', { timeout: longerCommandTimeout }).should(
      'not.exist'
    )
  })
}

const openColumnDropdown = (headerText: string) => {
  // Handsontable renders the sticky/frozen header via a separate clone pane
  // (.ht_clone_top) for scroll behavior; the header row inside the main
  // .ht_master pane is kept visibility:hidden (replaced visually by the
  // clone). .ht_clone_top is the real, interactive one — confirmed via
  // `document.querySelector('#hotTable .ht_clone_top thead').innerText` in
  // a live browser session.
  cy.get('#hotTable .ht_clone_top .htCore thead button.changeType', {
    timeout: longerCommandTimeout
  })
    .parents('th')
    .filter((_, th) => Cypress.$(th).text().includes(headerText))
    .last()
    .as('targetHeader')

  // Click the header text first to select the column — the `info` item's
  // renderer reads hot.getSelected() to decide which column to describe,
  // so without an active selection it always shows "No info found".
  cy.get('@targetHeader').click()
  cy.get('@targetHeader').find('button.changeType').click({ force: true })
}

const toggleColumnLabelsFromContextMenu = (
  menuItemText: 'Show labels' | 'Show names'
) => {
  cy.get('#hotTable .ht_master.handsontable .htCore tbody tr td', {
    timeout: longerCommandTimeout
  })
    .first()
    // force: true — the first body row can sit under an overlay clone's
    // header/sort-icon layer, which fails Cypress's actionability check
    // even though the cell is the real rightclick target underneath.
    .rightclick({ force: true })

  cy.get('.htContextMenu').contains(menuItemText).click()

  cy.get('.app-loading', { timeout: longerCommandTimeout }).should('not.exist')
}

const openTableFromTree = (libNameIncludes: string, tablename: string) => {
  cy.get('.app-loading', { timeout: longerCommandTimeout })
    .should('not.exist')
    .then(() => {
      cy.get('.nav-tree clr-tree > clr-tree-node', {
        timeout: longerCommandTimeout
      }).then((treeNodes: any) => {
        let viyaLib

        for (let node of treeNodes) {
          if (node.innerText.toLowerCase().includes(libNameIncludes)) {
            viyaLib = node
            break
          }
        }

        cy.get(viyaLib).within(() => {
          // Small settle wait: right after a fresh visit/reload, the tree
          // component can still be re-rendering, causing this node to be
          // found then swapped out mid-click ("disappeared from the page").
          cy.wait(300)

          cy.get(
            '.clr-tree-node-content-container .clr-treenode-content p'
          ).click()

          cy.get('.clr-treenode-link').then((innerNodes: any) => {
            for (let innerNode of innerNodes) {
              if (innerNode.innerText.toLowerCase().includes(tablename)) {
                innerNode.click()
                break
              }
            }
          })
        })
      })
    })

  // Selecting the table triggers async SPA routing + a viewdata fetch;
  // wait for the grid to actually render before any subsequent action
  // (reading the URL, right-clicking, opening a header dropdown) — without
  // this, callers can act while still on the intermediate library-only
  // route or a not-yet-rendered grid. Waiting specifically for a header's
  // dropdown button (not just the <tr> shell) confirms Handsontable has
  // finished populating header cell contents, not just their DOM rows.
  cy.get('#hotTable .ht_clone_top .htCore thead button.changeType', {
    timeout: longerCommandTimeout
  }).should('exist')
}
