import fs from 'fs'
import path from 'path'

import { ast } from '../../src'
import * as nodes from '../../src/ast/nodes'
import { NodeVisitor, walk } from '../../src/ast/visit'
import { loadYamlCases } from '../loaders/yaml'

const fixturesDir = path.resolve(__dirname, '..', 'fixtures')

class TestVisitor extends NodeVisitor {
  public visited: Map<string, number>

  constructor() {
    super()
    this.visited = new Map()
  }

  override genericVisit(node: nodes.ASTRootBase): void {
    this.visited.set(
      node.constructor.name,
      (this.visited.get(node.constructor.name) || 0) + 1,
    )
    return super.genericVisit(node)
  }
}

describe('NodeVisitor', () => {
  test('block.tlb can be visited', () => {
    expect.hasAssertions()

    const blockInput = fs.readFileSync(
      path.resolve(fixturesDir, 'tlb', 'block.tlb'),
      'utf-8',
    )
    const tree = ast(blockInput)
    expect(tree).toBeInstanceOf(nodes.Program)

    const visitor = new TestVisitor()
    visitor.visit(tree)

    expect(visitor.visited).not.toEqual(new Map())
  })

  for (
    let caseDef of loadYamlCases(
      fixturesDir,
      'ast',
      'visit.yml',
    )
  ) {
    test(`Generated visit example: ${caseDef.case}`, () => {
      expect.hasAssertions()

      const visitor = new TestVisitor()
      visitor.visit(ast(caseDef.code))

      const expected = Object.entries(JSON.parse(caseDef.result!))
      expect(visitor.visited).toEqual(new Map(expected))
    })
  }
})

describe('walk parents', () => {
  test('block.tlb can be visited', () => {
    expect.hasAssertions()

    const blockInput = fs.readFileSync(
      path.resolve(fixturesDir, 'tlb', 'block.tlb'),
      'utf-8',
    )
    const tree = ast(blockInput)
    expect(tree).toBeInstanceOf(nodes.Program)

    for (let node of walk(tree)) {
      if (node instanceof nodes.Program) {
        expect(node.parent).toBe(null)
      } else {
        expect(node).toBeInstanceOf(nodes.ASTBase)
        expect(node.parent).toBeInstanceOf(nodes.ASTRootBase)
      }
    }
  })
})
