const { resolve } = require('node:path')
const t = require('tap')
const { explainNode, printNode, explainEdge } = require('../../../lib/utils/explain-dep.js')
const { cleanCwd } = require('../../fixtures/clean-snapshot')

t.cleanSnapshot = (str) => cleanCwd(str)

const getCases = (testdir) => {
  const cases = {
    prodDep: {
      name: 'prod-dep',
      version: '1.2.3',
      location: 'node_modules/prod-dep',
      dependents: [
        {
          type: 'prod',
          name: 'prod-dep',
          spec: '1.x',
          from: {
            location: '/path/to/project',
          },
        },
      ],
    },

    deepDev: {
      name: 'deep-dev',
      version: '2.3.4',
      location: 'node_modules/deep-dev',
      dev: true,
      dependents: [
        {
          type: 'prod',
          name: 'deep-dev',
          spec: '2.x',
          from: {
            name: 'metadev',
            version: '3.4.5',
            location: 'node_modules/dev/node_modules/metadev',
            dependents: [
              {
                type: 'prod',
                name: 'metadev',
                spec: '3.x',
                from: {
                  name: 'topdev',
                  version: '4.5.6',
                  location: 'node_modules/topdev',
                  dependents: [
                    {
                      type: 'dev',
                      name: 'topdev',
                      spec: '4.x',
                      from: {
                        location: '/path/to/project',
                      },
                    },
                  ],
                },
              },
            ],
          },
        },
      ],
    },

    optional: {
      name: 'optdep',
      version: '1.0.0',
      location: 'node_modules/optdep',
      optional: true,
      dependents: [
        {
          type: 'optional',
          name: 'optdep',
          spec: '1.0.0',
          from: {
            location: '/path/to/project',
          },
        },
      ],
    },

    peer: {
      name: 'peer',
      version: '1.0.0',
      location: 'node_modules/peer',
      peer: true,
      dependents: [
        {
          type: 'peer',
          name: 'peer',
          spec: '1.0.0',
          from: {
            location: '/path/to/project',
          },
        },
      ],
    },

    bundled: {
      name: 'bundle-of-joy',
      version: '1.0.0',
      location: 'node_modules/bundle-of-joy',
      bundled: true,
      dependents: [
        {
          type: 'prod',
          name: 'prod-dep',
          spec: '1.x',
          bundled: true,
          from: {
            location: '/path/to/project',
          },
        },
      ],
    },

    extraneous: {
      name: 'extra-neos',
      version: '1337.420.69-lol',
      location: 'node_modules/extra-neos',
      dependents: [],
      extraneous: true,
    },

    overridden: {
      name: 'overridden-root',
      version: '1.0.0',
      location: 'node_modules/overridden-root',
      overridden: true,
      dependents: [{
        type: 'prod',
        name: 'overridden-dep',
        spec: '1.0.0',
        rawSpec: '^2.0.0',
        overridden: true,
        from: {
          location: '/path/to/project',
        },
      }],
    },

    packageExtensions: {
      name: 'bar',
      version: '1.2.3',
      location: 'node_modules/bar',
      dependents: [{
        type: 'prod',
        name: 'bar',
        spec: '^1.0.0',
        packageExtensions: { selector: 'foo@1', field: 'dependencies' },
        from: {
          name: 'foo',
          version: '1.0.0',
          location: 'node_modules/foo',
        },
      }],
    },

    npmExtension: {
      name: 'bar',
      version: '1.2.3',
      location: 'node_modules/bar',
      dependents: [{
        type: 'prod',
        name: 'bar',
        spec: '^1.0.0',
        npmExtension: { extensionPoint: 'transformManifest', field: 'dependencies' },
        from: {
          name: 'foo',
          version: '1.0.0',
          location: 'node_modules/foo',
        },
      }],
    },
  }

  cases.manyDeps = {
    name: 'manydep',
    version: '1.0.0',
    dependents: [
      {
        type: 'prod',
        name: 'manydep',
        spec: '1.0.0',
        from: cases.prodDep,
      },
      {
        type: 'optional',
        name: 'manydep',
        spec: '1.x',
        from: cases.optional,
      },
      {
        type: 'prod',
        name: 'manydep',
        spec: '1.0.x',
        from: cases.extraneous,
      },
      {
        type: 'dev',
        name: 'manydep',
        spec: '*',
        from: cases.deepDev,
      },
      {
        type: 'peer',
        name: 'manydep',
        spec: '>1.0.0-beta <1.0.1',
        from: cases.peer,
      },
      {
        type: 'prod',
        name: 'manydep',
        spec: '>1.0.0-beta <1.0.1',
        from: {
          location: '/path/to/project',
        },
      },
      {
        type: 'prod',
        name: 'manydep',
        spec: '1',
        from: {
          name: 'a package with a pretty long name',
          version: '1.2.3',
          dependents: {
            location: '/path/to/project',
          },
        },
      },
      {
        type: 'prod',
        name: 'manydep',
        spec: '1',
        from: {
          name: 'another package with a pretty long name',
          version: '1.2.3',
          dependents: {
            location: '/path/to/project',
          },
        },
      },
      {
        type: 'prod',
        name: 'manydep',
        spec: '1',
        from: {
          name: 'yet another a package with a pretty long name',
          version: '1.2.3',
          dependents: {
            location: '/path/to/project',
          },
        },
      },
    ],
  }

  cases.workspaces = {
    name: 'a',
    version: '1.0.0',
    location: 'a',
    isWorkspace: true,
    dependents: [],
    linksIn: [
      {
        name: 'a',
        version: '1.0.0',
        location: 'node_modules/a',
        isWorkspace: true,
        dependents: [
          {
            type: 'workspace',
            name: 'a',
            spec: `file:${resolve(testdir, 'ws-project', 'a')}`,
            from: { location: resolve(testdir, 'ws-project') },
          },
        ],
      },
    ],
  }

  return cases
}

t.test('basic', async t => {
  const { Chalk } = await import('chalk')
  const color = new Chalk({ level: 3 })
  const noColor = new Chalk({ level: 0 })

  const testdir = t.testdir()
  const cases = getCases(testdir)

  for (const [name, expl] of Object.entries(getCases(testdir))) {
    t.test(name, t => {
      t.matchSnapshot(printNode(expl, color), 'print color')
      t.matchSnapshot(printNode(expl, noColor), 'print nocolor')
      t.matchSnapshot(explainNode(expl, Infinity, color), 'explain color deep')
      t.matchSnapshot(explainNode(expl, 2, noColor), 'explain nocolor shallow')
      t.end()
    })
  }

  // Regression test for https://github.com/npm/cli/issues/9109
  // Circular dependency graphs (common in linked strategy store nodes) should not cause infinite recursion in explainNode.
  const cycleA = {
    name: 'cycle-a',
    version: '1.0.0',
    location: 'node_modules/cycle-a',
    dependents: [],
  }
  const cycleB = {
    name: 'cycle-b',
    version: '2.0.0',
    location: 'node_modules/cycle-b',
    dependents: [{
      type: 'prod',
      name: 'cycle-b',
      spec: '2.x',
      from: cycleA,
    }],
  }
  cycleA.dependents = [{
    type: 'prod',
    name: 'cycle-a',
    spec: '1.x',
    from: cycleB,
  }]
  t.matchSnapshot(explainNode(cycleA, Infinity, noColor), 'circular dependency does not recurse infinitely')
  t.matchSnapshot(explainNode(cycleB, Infinity, noColor), 'circular dependency from other side')

  // explainEdge called without seen parameter (covers default seen = new Set() branch on explainEdge and explainFrom)
  t.matchSnapshot(explainEdge({
    type: 'prod',
    name: 'some-dep',
    spec: '1.x',
    from: {
      name: 'parent-pkg',
      version: '2.0.0',
      location: 'node_modules/parent-pkg',
      dependents: [],
    },
  }, 2, noColor), 'explainEdge without seen parameter')

  // make sure that we show the last one if it's the only one that would
  // hit the ...
  cases.manyDeps.dependents.pop()
  t.matchSnapshot(explainNode(cases.manyDeps, 2, noColor), 'ellipses test one')
  cases.manyDeps.dependents.pop()
  t.matchSnapshot(explainNode(cases.manyDeps, 2, noColor), 'ellipses test two')
})
