/**
 * Utilities for assembling the Rollup plugin pipeline used by the Effect
 * bundle-size tooling.
 *
 * This module is responsible for the bundler-specific work that turns local
 * fixture entrypoints into comparable ESM output: resolving Effect package
 * imports against each package's built `dist` files, replacing production
 * environment checks, lowering TypeScript with esbuild, minifying with terser,
 * and optionally adding a bundle visualizer. It is primarily used by the
 * Rollup service when measuring gzipped fixture sizes or opening a
 * visualization for bundle inspection.
 *
 * Keep plugin ordering intentional when changing this module. Local package
 * resolution must run before normal node resolution so workspace imports are
 * measured from built artifacts, esbuild must emit ESM for Rollup to continue
 * tree-shaking, and terser mangling is disabled while visualizing so reported
 * module names stay readable.
 *
 * @since 4.0.0
 */
import { nodeResolve } from "@rollup/plugin-node-resolve"
import replace from "@rollup/plugin-replace"
import terser from "@rollup/plugin-terser"
import type * as Path from "effect/Path"
import * as Predicate from "effect/Predicate"
import type { Plugin } from "rollup"
import esbuild from "rollup-plugin-esbuild"
import { type PluginVisualizerOptions, visualizer } from "rollup-plugin-visualizer"

const EFFECT_PACKAGE_REGEX = /^(@effect\/[\w-]+|effect)(\/.*)?$/
const TYPE_SCRIPT_EXTENSIONS = new Set([".ts", ".tsx", ".mts", ".cts"])

const toLocalDistPath = (pathService: Path.Path, packageDir: string, resolvedId: string): string => {
  const srcDir = pathService.join(packageDir, "src")
  const relative = pathService.relative(srcDir, resolvedId)
  if (relative === "" || relative.startsWith("..") || pathService.isAbsolute(relative)) {
    return resolvedId
  }
  const extension = pathService.extname(relative)
  if (!TYPE_SCRIPT_EXTENSIONS.has(extension)) {
    return resolvedId
  }
  return pathService.join(packageDir, "dist", relative.slice(0, -extension.length) + ".js")
}

/**
 * Options for configuring Rollup plugins.
 *
 * @category options
 * @since 4.0.0
 */
export interface PluginOptions {
  readonly nodeTarget?: string | undefined
  readonly minify?: boolean | undefined
  readonly mangle?: boolean | undefined
  readonly visualize?: boolean | undefined
  readonly visualizations?: ReadonlyArray<VisualizationOutput> | undefined
}

/**
 * Output generated by the Rollup visualizer plugin.
 *
 * @category options
 * @since 4.0.0
 */
export interface VisualizationOutput {
  readonly filename: string
  readonly template: NonNullable<PluginVisualizerOptions["template"]>
  readonly title?: string | undefined
}

interface ResolvedPluginOptions {
  readonly nodeTarget: string
  readonly minify: boolean
  readonly mangle: boolean
  readonly visualize: boolean
  readonly visualizations: ReadonlyArray<VisualizationOutput>
}

const defaultPluginOptions: ResolvedPluginOptions = {
  nodeTarget: "node20",
  minify: true,
  mangle: true,
  visualize: false,
  visualizations: []
}

/**
 * Merges provided options with defaults.
 */
const resolvePluginOptions = (options: PluginOptions): ResolvedPluginOptions => ({
  nodeTarget: options.nodeTarget ?? defaultPluginOptions.nodeTarget,
  minify: options.minify ?? defaultPluginOptions.minify,
  mangle: options.mangle ?? defaultPluginOptions.mangle,
  visualize: options.visualize ?? defaultPluginOptions.visualize,
  visualizations: options.visualizations ?? defaultPluginOptions.visualizations
})

/**
 * Creates a custom Rollup plugin that resolves Effect package imports to their
 * local dist directories.
 *
 * @category constructors
 * @since 4.0.0
 */
export const createResolveLocalPackageImports = (pathService: Path.Path): Plugin => ({
  name: "rollup-plugin-resolve-imports",
  async resolveId(source, importer) {
    const match = source.match(EFFECT_PACKAGE_REGEX)
    if (Predicate.isNotNull(match)) {
      const packageName = match[1]
      const packageJson = await this.resolve(`${packageName}/package.json`, importer, { skipSelf: true })
      if (packageJson === null) return null
      const resolved = await this.resolve(source, importer, { skipSelf: true })
      if (resolved === null) return null
      return {
        ...resolved,
        id: toLocalDistPath(pathService, pathService.dirname(packageJson.id), resolved.id),
        external: false
      }
    }
    return null
  }
})

/**
 * Creates the full Rollup plugin pipeline for bundling.
 *
 * @category constructors
 * @since 4.0.0
 */
export const createPlugins = (pathService: Path.Path, options: PluginOptions = {}): Array<Plugin> => {
  const resolved = resolvePluginOptions(options)
  const plugins: Array<Plugin> = [
    createResolveLocalPackageImports(pathService),
    nodeResolve(),
    // @ts-expect-error see https://github.com/rollup/plugins/issues/1662
    replace({
      "process.env.NODE_ENV": JSON.stringify("production"),
      preventAssignment: true
    }),
    esbuild({
      target: resolved.nodeTarget,
      format: "esm",
      treeShaking: true
    }),
    // @ts-expect-error see https://github.com/rollup/plugins/issues/1662
    terser({
      format: { comments: false },
      compress: resolved.minify,
      mangle: resolved.mangle && !resolved.visualize
    })
  ]

  if (resolved.visualizations.length > 0) {
    for (const output of resolved.visualizations) {
      const visualizerOptions: PluginVisualizerOptions = {
        filename: output.filename,
        gzipSize: true,
        open: false,
        template: output.template
      }
      if (output.title !== undefined) {
        visualizerOptions.title = output.title
      }
      plugins.push(visualizer(visualizerOptions))
    }
  } else if (resolved.visualize) {
    plugins.push(visualizer({
      open: true,
      gzipSize: true
    }))
  }

  return plugins
}
