import path from 'path'
import { bracket } from '@e2e/playwright/fixtures/bracket'
import type { Page } from '@playwright/test'
import { reportRejection } from '@src/lib/trap'
import * as fsp from 'fs/promises'

import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from '@e2e/playwright/storageStates'
import type { TestColor } from '@e2e/playwright/test-utils'
import {
  TEST_COLORS,
  executorInputPath,
  getUtils,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'

test.describe('Regression tests', () => {
  // bugs we found that don't fit neatly into other categories
  test('bad model has inline error #3251', async ({
    context,
    page,
    homePage,
    scene,
  }) => {
    // because the model has `line([0,0]..` it is valid code, but the model is invalid
    // regression test for https://github.com/KittyCAD/modeling-app/issues/3251
    // Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
    // const u = await getUtils(page)
    await context.addInitScript(async () => {
      localStorage.setItem(
        'persistCode',
        `sketch2 = startSketchOn(XY)
  sketch001 = startSketchOn(XY)
    |> startProfile(at = [-0, -0])
    |> line(end = [0, 0])
    |> line(end = [-4.84, -5.29])
    |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
    |> close()`
      )
    })

    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await scene.connectionEstablished()
    // await u.waitForPageLoad()

    // error in guter
    await expect(page.locator('.cm-lint-marker-error')).toBeVisible()

    // error text on hover
    await page.hover('.cm-lint-marker-error')
    // this is a cryptic error message, fact that all the lines are co-linear from the `line([0,0])` is the issue why
    // the close doesn't work
    // when https://github.com/KittyCAD/modeling-app/issues/3268 is closed
    // this test will need updating
    const crypticErrorText = `Cannot close a path that is non-planar or with duplicate vertices.
Internal engine error on request`
    await expect(page.getByText(crypticErrorText).first()).toBeVisible()
    // Ensure we didn't nest the json.
    await expect(page.getByText('ApiError')).not.toBeVisible()
  })
  test('user should not have to press down twice in cmdbar', async ({
    page,
    homePage,
  }) => {
    // because the model has `line([0,0]..` it is valid code, but the model is invalid
    // regression test for https://github.com/KittyCAD/modeling-app/issues/3251
    // Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
    const u = await getUtils(page)
    await page.addInitScript(async () => {
      localStorage.setItem(
        'persistCode',
        `sketch001 = startSketchOn(XY)
  |> startProfile(at = [82.33, 238.21])
  |> angledLine(angle = 0, length = 288.63, tag = $rectangleSegmentA001)
  |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 197.97, tag = $rectangleSegmentB001)
  |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
  |> close()
extrude001 = extrude(sketch001, length = 50)
`
      )
    })

    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    await test.step('Check arrow down works', async () => {
      await page.getByTestId('command-bar-open-button').hover()
      await page.getByTestId('command-bar-open-button').click()

      const floppy = page.getByRole('option', {
        name: 'floppy disk arrow Export',
      })

      await floppy.click()

      // press arrow down key twice
      await page.keyboard.press('ArrowDown')
      await page.waitForTimeout(100)
      await page.keyboard.press('ArrowDown')

      // STL is the third option, which makes sense for two arrow downs
      await expect(page.locator('[data-headlessui-state="active"]')).toHaveText(
        'STL'
      )

      await page.keyboard.press('Escape')
      await page.waitForTimeout(200)
      await page.keyboard.press('Escape')
      await page.waitForTimeout(200)
    })

    await test.step('Check arrow up works', async () => {
      // theme in test is dark, which is the second option, which means we can test arrow up
      await page.getByTestId('command-bar-open-button').click()

      await page.getByText('The overall appearance of the').click()

      await page.keyboard.press('ArrowUp')
      await page.waitForTimeout(100)

      await expect(page.locator('[data-headlessui-state="active"]')).toHaveText(
        'light'
      )
    })
  })
  test('executes on load', async ({ page, homePage }) => {
    const u = await getUtils(page)
    await page.addInitScript(async () => {
      localStorage.setItem(
        'persistCode',
        `sketch001 = startSketchOn(-XZ)
  |> startProfile(at = [-6.95, 4.98])
  |> line(end = [25.1, 0.41])
  |> line(end = [0.73, -14.93])
  |> line(end = [-23.44, 0.52])`
      )
    })
    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    // expand variables section
    const variablesTabButton = page.getByTestId('variables-pane-button')
    await variablesTabButton.click()

    // can find sketch001 in the variables summary (pretty-json-container, makes sure we're not looking in the code editor)
    // sketch001 only shows up in the variables summary if it's been executed
    await page.waitForFunction(() => {
      const variablesElement = document.querySelector(
        '.pretty-json-container'
      ) as HTMLDivElement
      return variablesElement.innerHTML.includes('sketch001')
    })
    await expect(
      page.locator('.pretty-json-container >> text=sketch001')
    ).toBeVisible()
  })

  test('re-executes', async ({ page, homePage }) => {
    const u = await getUtils(page)
    await page.addInitScript(async () => {
      localStorage.setItem('persistCode', `myVar = 5`)
    })
    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    const variablesTabButton = page.getByTestId('variables-pane-button')
    await variablesTabButton.click()
    // expect to see "myVar:5"
    await expect(
      // There's a double quote before the number value since the units are
      // formatted into a string.
      page.locator('.pretty-json-container >> text=myVar:"5')
    ).toBeVisible()

    // change 5 to 67
    await page.locator('#code-mirror-override').getByText('myVar').click()
    await page.keyboard.press('End')
    await page.keyboard.press('Backspace')
    await page.keyboard.type('67')

    await expect(
      page.locator('.pretty-json-container >> text=myVar:"67')
    ).toBeVisible()
  })
  test('ProgramMemory can be serialised', async ({ page, homePage, scene }) => {
    // const u = await getUtils(page)
    await page.addInitScript(async () => {
      localStorage.setItem(
        'persistCode',
        `part = startSketchOn(XY)
  |> startProfile(at = [0, 0])
  |> line(end = [0, 1])
  |> line(end = [1, 0])
  |> line(end = [0, -1])
  |> close()
  |> extrude(length = 1)
  |> patternLinear3d(
        axis = [1, 0, 1],
        repetitions = 3,
        distance = 6,
      )`
      )
    })
    await page.setBodyDimensions({ width: 1000, height: 500 })

    const messages: string[] = []

    // Listen for all console events and push the message text to an array
    page.on('console', (message) => messages.push(message.text()))
    await homePage.goToModelingScene()
    // await u.waitForPageLoad()
    await scene.connectionEstablished()

    // wait for execution done
    // await u.openDebugPanel()
    // await u.expectCmdLog('[data-message-type="execution-done"]')

    const forbiddenMessages = ['cannot serialize tagged newtype variant']
    forbiddenMessages.forEach((forbiddenMessage) => {
      messages.forEach((message) => {
        expect(message).not.toContain(forbiddenMessage)
      })
    })
  })

  test('Position _ Is Out Of Range... regression test', async ({
    context,
    page,
    homePage,
    scene,
  }) => {
    const u = await getUtils(page)
    // const PUR = 400 / 37.5 //pixeltoUnitRatio
    await page.setBodyDimensions({ width: 1200, height: 500 })
    await context.addInitScript(async () => {
      localStorage.setItem(
        'persistCode',
        `exampleSketch = startSketchOn(XZ)
      |> startProfile(at = [0, 0])
      |> angledLine(angle = 50, length = 45 )
      |> yLine(endAbsolute = 0)
      |> close()
      |>

    example = extrude(exampleSketch, length = 5)
    shell(exampleSketch, faces = ['end'], thickness = 0.25)`
      )
    })
    await homePage.goToModelingScene()
    await scene.connectionEstablished()

    await expect(async () => {
      // error in guter
      await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
        timeout: 1_000,
      })
      await page.waitForTimeout(200)
      // expect it still to be there (sometimes it just clears for a bit?)
      await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
        timeout: 1_000,
      })
    }).toPass({ timeout: 40_000, intervals: [1_000] })

    // error text on hover
    await page.hover('.cm-lint-marker-error')
    await expect(page.getByText('Unexpected token: |').first()).toBeVisible()

    // Okay execution finished, let's start editing text below the error.
    await u.codeLocator.click()
    // Go to the end of the editor
    // This bug happens when there is a diagnostic in the editor and you try to
    // edit text below it.
    // Or delete a huge chunk of text and then try to edit below it.
    await page.keyboard.press('End')
    await page.keyboard.down('Shift')
    await page.keyboard.press('ArrowUp')
    await page.keyboard.press('ArrowUp')
    await page.keyboard.press('ArrowUp')
    await page.keyboard.press('ArrowUp')
    await page.keyboard.press('ArrowUp')
    await page.keyboard.press('End')
    await page.keyboard.up('Shift')
    await page.keyboard.press('Backspace')
    await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()

    await page.keyboard.press('Enter')
    await page.keyboard.press('Enter')
    await page.keyboard.type('thing: "blah"', { delay: 100 })
    await page.keyboard.press('Enter')
    await page.keyboard.press('ArrowLeft')

    await expect(
      page.locator('.cm-content')
    ).toContainText(`exampleSketch = startSketchOn(XZ)
      |> startProfile(at = [0, 0])
      |> angledLine(angle = 50, length = 45 )
      |> yLine(endAbsolute = 0)
      |> close()

      thing: "blah"`)

    await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
  })

  test(
    'window resize updates should reconfigure the stream',
    { tag: '@skipLocalEngine' },
    async ({ scene, page, homePage, cmdBar, toolbar }) => {
      await page.addInitScript(
        async ({ code }) => {
          localStorage.setItem(
            'persistCode',
            `@settings(defaultLengthUnit = mm)
sketch002 = startSketchOn(XY)
profile002 = startProfile(sketch002, at = [72.24, -52.05])
  |> angledLine(angle = 0, length = 181.26, tag = $rectangleSegmentA001)
  |> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 21.54)
  |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001))
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
  |> close()
extrude002 = extrude(profile002, length = 150)
`
          )
          ;(window as any).playwrightSkipFilePicker = true
        },
        { code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
      )

      const websocketPromise = page.waitForEvent('websocket')
      await page.setBodyDimensions({ width: 1000, height: 500 })

      await homePage.goToModelingScene()
      await toolbar.closePane('code')
      const websocket = await websocketPromise

      await scene.connectionEstablished()
      await scene.settled(cmdBar)
      await page.setBodyDimensions({ width: 500, height: 500 })

      // expect pixel color to be background color
      const offModelBefore = { x: 446, y: 250 }
      const onModelBefore = { x: 422, y: 250 }
      const offModelAfter = { x: 692, y: 262 }
      const onModelAfter = { x: 673, y: 266 }

      await scene.expectPixelColor(
        TEST_COLORS.DARK_MODE_BKGD,
        offModelBefore,
        15
      )
      const standardModelGrey: TestColor = [100, 100, 100]
      await scene.expectPixelColor(standardModelGrey, onModelBefore, 15)

      await test.step('resize window and expect "reconfigure_stream" websocket message', async () => {
        // note this is a bit low level for our tests, usually this would go into a fixture
        // but it's pretty unique to this resize test, it can be moved/abstracted if we have further need
        // to listen to websocket messages
        await Promise.all([
          new Promise((resolve) => {
            websocket
              // @ts-ignore
              .waitForEvent('framesent', (frame) => {
                frame.payload
                  .toString()
                  .includes(
                    '"type":"reconfigure_stream","width":1000,"height":500'
                  ) && resolve(true)
              })
              .catch(reportRejection)
          }),
          page.setBodyDimensions({ width: 1000, height: 500 }),
        ])
      })

      await scene.expectPixelColor(
        TEST_COLORS.DARK_MODE_BKGD,
        offModelAfter,
        15
      )
      await scene.expectPixelColor(standardModelGrey, onModelAfter, 15)
    }
  )
  test(
    'when engine fails export we handle the failure and alert the user',
    { tag: '@skipLocalEngine' },
    async ({ scene, page, homePage, cmdBar }) => {
      await page.addInitScript(
        async ({ code }) => {
          localStorage.setItem('persistCode', code)
          ;(window as any).playwrightSkipFilePicker = true
        },
        { code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
      )

      await page.setBodyDimensions({ width: 1000, height: 500 })

      await homePage.goToModelingScene()

      await scene.connectionEstablished()
      await scene.settled(cmdBar)

      // export the model
      const exportButton = page.getByTestId('export-pane-button')
      await expect(exportButton).toBeVisible()

      // Click the export button
      await exportButton.click()

      // Click the stl.
      const stlOption = page.getByText('glTF')
      await expect(stlOption).toBeVisible()

      await page.keyboard.press('Enter')

      // Click the checkbox
      const submitButton = page.getByText('Confirm Export')
      await expect(submitButton).toBeVisible()

      await page.keyboard.press('Enter')

      // Find the toast.
      // Look out for the toast message
      const exportingToastMessage = page.getByText(`Exporting...`)

      const engineErrorToastMessage = page.getByText(`Nothing to export`)
      await expect(engineErrorToastMessage).toBeVisible()

      // Make sure the exporting toast is gone
      await expect(exportingToastMessage).not.toBeVisible()

      // Click the code editor
      await page.locator('.cm-content').click()

      await page.waitForTimeout(2000)

      // Expect the toast to be gone
      await expect(engineErrorToastMessage).not.toBeVisible()

      // Now add in code that works.
      await page.locator('.cm-content').fill(bracket)
      await page.keyboard.press('End')
      await page.keyboard.press('Enter')

      await scene.settled(cmdBar)

      // Now try exporting

      // Click the export button
      await exportButton.click()

      // Click the stl.
      await expect(stlOption).toBeVisible()
      await page.keyboard.press('Enter')

      // Click the checkbox
      await expect(submitButton).toBeVisible()
      await page.keyboard.press('Enter')

      // Find the toast.
      // Look out for the toast message
      await expect(exportingToastMessage).toBeVisible()

      // Expect it to succeed.
      await expect(exportingToastMessage).not.toBeVisible()
      await expect(engineErrorToastMessage).not.toBeVisible()

      const successToastMessage = page.getByText(`Exported successfully`)
      await page.waitForTimeout(1_000)
      const count = await successToastMessage.count()
      await expect(count).toBeGreaterThanOrEqual(1)
    }
  )
  // We updated this test such that you can have multiple exports going at once.
  test('ensure you CAN export while an export is already going', async ({
    page,
    homePage,
  }) => {
    const u = await getUtils(page)
    await test.step('Set up the code and durations', async () => {
      await page.addInitScript(
        async ({ code }) => {
          localStorage.setItem('persistCode', code)
          ;(window as any).playwrightSkipFilePicker = true
        },
        {
          code: bracket,
        }
      )

      await page.setBodyDimensions({ width: 1000, height: 500 })

      await homePage.goToModelingScene()
      await u.waitForPageLoad()

      // wait for execution done
      await u.openDebugPanel()
      await u.expectCmdLog('[data-message-type="execution-done"]')
      await u.closeDebugPanel()

      // expect zero errors in guter
      await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
    })

    const errorToastMessage = page.getByText(`Error while exporting`)
    const exportingToastMessage = page.getByText(`Exporting...`)
    const engineErrorToastMessage = page.getByText(`Nothing to export`)
    const alreadyExportingToastMessage = page.getByText(`Already exporting`)
    const successToastMessage = page.getByText(`Exported successfully`)

    await test.step('second export', async () => {
      await clickExportButton(page)

      await expect(exportingToastMessage).toBeVisible()

      await clickExportButton(page)

      await test.step('The first export still succeeds', async () => {
        await Promise.all([
          expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }),
          expect(errorToastMessage).not.toBeVisible(),
          expect(engineErrorToastMessage).not.toBeVisible(),
          expect(successToastMessage).toBeVisible({ timeout: 15_000 }),
          expect(alreadyExportingToastMessage).not.toBeVisible({
            timeout: 15_000,
          }),
        ])
      })
    })

    await test.step('Successful, unblocked export', async () => {
      // Try exporting again.
      await clickExportButton(page)

      // Find the toast.
      // Look out for the toast message
      await expect(exportingToastMessage).toBeVisible()

      // Expect it to succeed.
      await Promise.all([
        expect(exportingToastMessage).not.toBeVisible(),
        expect(errorToastMessage).not.toBeVisible(),
        expect(engineErrorToastMessage).not.toBeVisible(),
        expect(alreadyExportingToastMessage).not.toBeVisible(),
      ])

      const count = await successToastMessage.count()
      await expect(count).toBeGreaterThanOrEqual(2)
    })
  })

  test(
    `Network health indicator only appears in modeling view`,
    { tag: '@electron' },
    async ({ context, page }) => {
      await context.folderSetupFn(async (dir) => {
        const bracketDir = path.join(dir, 'bracket')
        await fsp.mkdir(bracketDir, { recursive: true })
        await fsp.copyFile(
          executorInputPath('cylinder-inches.kcl'),
          path.join(bracketDir, 'main.kcl')
        )
      })
      await page.setBodyDimensions({ width: 1200, height: 500 })

      // Locators
      const projectsHeading = page.getByRole('heading', {
        name: 'Projects',
      })
      const projectLink = page.getByRole('link', { name: 'bracket' })
      const networkHealthIndicator = page.getByTestId('network-toggle')

      await test.step('Check the home page', async () => {
        await expect(projectsHeading).toBeVisible()
        await expect(projectLink).toBeVisible()
        await expect(networkHealthIndicator).not.toBeVisible()
      })

      await test.step('Open the project', async () => {
        await projectLink.click()
      })

      await test.step('Check the modeling view', async () => {
        await expect(projectsHeading).not.toBeVisible()
        await expect(networkHealthIndicator).toBeVisible()
      })
    }
  )

  test(`View gizmo stays visible even when zoomed out all the way`, async ({
    page,
    homePage,
    scene,
  }) => {
    const u = await getUtils(page)

    // Constants and locators
    const planeColor: [number, number, number] = [170, 220, 170]
    const bgColor: [number, number, number] = TEST_COLORS.DARK_MODE_BKGD
    const middlePixelIsColor = async (color: [number, number, number]) => {
      return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
    }
    const gizmo = page.locator('[aria-label*=gizmo]')

    await test.step(`Load an empty file`, async () => {
      await page.addInitScript(async () => {
        localStorage.setItem('persistCode', '@settings(defaultLengthUnit = in)')
      })
      await page.setBodyDimensions({ width: 1200, height: 500 })
      await homePage.goToModelingScene()
      await u.waitForPageLoad()
      await u.closeKclCodePanel()
    })

    await test.step(`Zoom out until you can't see the default planes`, async () => {
      await expect
        .poll(async () => middlePixelIsColor(planeColor), {
          timeout: 5000,
          message: 'Plane color is visible',
        })
        .toBeLessThanOrEqual(20)
      await expect(scene.startEditSketchBtn).toBeEnabled()

      let maxZoomOuts = 10
      let middlePixelIsBackgroundColor =
        (await middlePixelIsColor(bgColor)) < 10

      console.time('pressing control')
      await page.keyboard.down('Control')

      while (!middlePixelIsBackgroundColor && maxZoomOuts > 0) {
        await page.waitForTimeout(100)
        await page.mouse.move(650, 460)
        console.time('moved to start point')
        await page.mouse.down({ button: 'right' })
        console.time('moused down')
        await page.mouse.move(650, 50, { steps: 20 })
        console.time('moved to end point')
        await page.waitForTimeout(100)
        await page.mouse.up({ button: 'right' })
        console.time('moused up')
        maxZoomOuts--
        middlePixelIsBackgroundColor = (await middlePixelIsColor(bgColor)) < 15
      }
      await page.keyboard.up('Control')

      expect(middlePixelIsBackgroundColor, {
        message: 'We should not see the default planes',
      }).toBeTruthy()
    })

    await test.step(`Check that the gizmo is still visible`, async () => {
      await expect(gizmo).toBeVisible()
    })
  })

  test(`Refreshing the app doesn't cause the stream to pause on long-executing files`, async ({
    context,
    homePage,
    scene,
    toolbar,
    viewport,
  }) => {
    await context.folderSetupFn(async (dir) => {
      const legoDir = path.join(dir, 'lego')
      await fsp.mkdir(legoDir, { recursive: true })
      await fsp.copyFile(
        executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
        path.join(legoDir, 'main.kcl')
      )
    })

    await test.step(`Test setup`, async () => {
      await homePage.openProject('lego')
      await toolbar.closePane('code')
    })
    await test.step(`Waiting for scene to settle`, async () => {
      await scene.connectionEstablished()
    })
    await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
      // TODO: use the viewport size to pick the center point, but the `viewport` fixture's values were wrong.
      await scene.expectPixelColor([116, 116, 116], { x: 500, y: 250 }, 15)
    })
  })

  test(`Toolbar doesn't show modeling tools during sketch plane selection animation`, async ({
    page,
    homePage,
    toolbar,
  }) => {
    await test.step('Load an empty file', async () => {
      await page.addInitScript(async () => {
        localStorage.setItem('persistCode', '')
      })
      await page.setBodyDimensions({ width: 1200, height: 500 })
      await homePage.goToModelingScene()
    })

    await test.step('Start sketch and select a plane', async () => {
      await toolbar.expectToolbarMode.toBe('modeling')
      // Click the start sketch button
      await toolbar.startSketchPlaneSelection()

      // Click on a default plane at position [700, 200]
      await page.mouse.click(700, 200)

      // Check that the modeling toolbar doesn't appear during the animation
      // The animation typically takes around 500ms, so we'll check for a second
      await toolbar.expectToolbarMode.not.toBe('modeling')

      // After animation completes, we should see the sketching toolbar
      await toolbar.expectToolbarMode.toBe('sketching')
    })
  })

  test(`Delete via feature tree then open code pane, never crash`, async ({
    homePage,
    toolbar,
    editor,
    context,
    page,
    scene,
    cmdBar,
  }) => {
    await context.folderSetupFn(async (dir) => {
      const bracketDir = path.join(dir, 'test-sample')
      await fsp.mkdir(bracketDir, { recursive: true })
      await fsp.writeFile(
        path.join(bracketDir, 'main.kcl'),
        `x = 5
plane001 = offsetPlane(XZ, offset = x)
plane002 = offsetPlane(XZ, offset = -2 * x)`
      )
    })
    await homePage.openProject('test-sample')
    await scene.settled(cmdBar)
    await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 20_000 })
    const operationButton = await toolbar.getFeatureTreeOperation(
      'Offset Plane',
      1
    )

    await test.step('Delete offset plane via feature tree selection', async () => {
      await expect(operationButton.last()).toBeVisible({ timeout: 10_000 })
      await editor.closePane()
      await operationButton.first().click({ button: 'left' })
      await page.keyboard.press('Delete')
      await scene.settled(cmdBar)
    })

    await test.step(`Open the code pane, don't crash`, async () => {
      await editor.openPane()
      await expect(page.getByText('unexpected error')).not.toBeVisible()
      await editor.expectEditor.toContain(`x = 5`)
      await editor.expectEditor.toContain(`plane001`)
      await editor.expectEditor.not.toContain(`plane002`)
    })
  })

  test.fail(
    'Console errors cause tests to fail',
    async ({ page, homePage }) => {
      const u = await getUtils(page)
      await homePage.goToModelingScene()
      await u.openAndClearDebugPanel()

      await page.getByTestId('custom-cmd-input').fill('foobar')
      await page.getByTestId('custom-cmd-send-button').scrollIntoViewIfNeeded()
      await page.getByTestId('custom-cmd-send-button').click()
    }
  )

  test('scale other than default works with sketch mode', async ({
    page,
    homePage,
    toolbar,
    editor,
    scene,
  }) => {
    await test.step('Load the washer code', async () => {
      await page.addInitScript(async () => {
        localStorage.setItem(
          'persistCode',
          `@settings(defaultLengthUnit = in)

innerDiameter = 0.203
outerDiameter = 0.438
thicknessMax = 0.038
thicknessMin = 0.024
washerSketch = startSketchOn(XY)
  |> circle(center = [0, 0], radius = outerDiameter / 2)

washer = extrude(washerSketch, length = thicknessMax)`
        )
      })
      await page.setBodyDimensions({ width: 1200, height: 500 })
      await homePage.goToModelingScene()
    })
    const [circleCenterClick] = scene.makeMouseHelpers(650, 300)
    const [circleRadiusClick] = scene.makeMouseHelpers(800, 320)
    const [washerFaceClick] = scene.makeMouseHelpers(657, 286)

    await page.waitForTimeout(100)
    await test.step('Start sketching on the washer face', async () => {
      await toolbar.startSketchPlaneSelection()
      await washerFaceClick()
      await page.waitForTimeout(600) // engine animation
      await toolbar.expectToolbarMode.toBe('sketching')
    })

    await test.step('Draw a circle and verify code', async () => {
      // select circle tool
      await expect
        .poll(async () => {
          await toolbar.circleBtn.click()
          return toolbar.circleBtn.getAttribute('aria-pressed')
        })
        .toBe('true')
      await page.waitForTimeout(100)
      await circleCenterClick()
      // this number will be different if the scale is not set correctly for inches
      await editor.expectEditor.toContain(
        'circle(sketch001, center = [0.06, -0.06]'
      )
      await circleRadiusClick()

      await editor.expectEditor.toContain(
        'circle(sketch001, center = [0.06, -0.06], radius = 0.18'
      )
    })

    await test.step('Exit sketch mode', async () => {
      await toolbar.exitSketch()
      await toolbar.expectToolbarMode.toBe('modeling')

      await toolbar.selectUnit('Yards')
      await editor.expectEditor.toContain('@settings(defaultLengthUnit = yd)')
    })
  })

  test('Exiting existing sketch without editing should not delete it', async ({
    page,
    editor,
    homePage,
    context,
    toolbar,
    scene,
    cmdBar,
  }) => {
    await context.folderSetupFn(async (dir) => {
      const testDir = path.join(dir, 'test')
      await fsp.mkdir(testDir, { recursive: true })
      await fsp.writeFile(
        path.join(testDir, 'main.kcl'),
        `s1 = startSketchOn(XY)
  |> startProfile(at = [0, 25])
  |> xLine(endAbsolute = -15 + 1.5)
s2 = startSketchOn(XY)
  |> startProfile(at = [25, 0])
  |> yLine(endAbsolute = -15 + 1.5)`,
        'utf-8'
      )
    })

    await homePage.openProject('test')
    await scene.settled(cmdBar)
    await toolbar.waitForFeatureTreeToBeBuilt()
    await toolbar.editSketch(1)
    await page.waitForTimeout(1000) // Just hang out for a second
    await toolbar.exitSketch()

    await editor.expectEditor.toContain('s2 = startSketchOn(XY)')
  })
})

async function clickExportButton(page: Page) {
  await test.step('Running export flow', async () => {
    // export the model
    const exportButton = page.getByTestId('export-pane-button')
    await expect(exportButton).toBeEnabled()

    // Click the export button
    await exportButton.click()

    // Click the gltf.
    const gltfOption = page.getByRole('option', { name: 'glTF' })
    await expect(gltfOption).toBeVisible()

    await page.keyboard.press('Enter')

    // Click the checkbox
    const submitButton = page.getByText('Confirm Export')
    await expect(submitButton).toBeVisible()

    await page.keyboard.press('Enter')
  })
}
