import { PerfTimer } from "../util/perf"
import { getStaticResourcesFromPlugins } from "../plugins"
import { ProcessedContent } from "../plugins/vfile"
import { QuartzEmitterPluginInstance } from "../plugins/types"
import { QuartzLogger } from "../util/log"
import { trace } from "../util/trace"
import { BuildCtx } from "../util/ctx"
import { StaticResources } from "../util/resources"
import { styleText } from "util"

async function runEmitter(
  emitter: QuartzEmitterPluginInstance,
  ctx: BuildCtx,
  content: ProcessedContent[],
  resources: StaticResources,
  log: QuartzLogger,
): Promise<number> {
  let count = 0
  try {
    const emitted = await emitter.emit(ctx, content, resources)
    if (Symbol.asyncIterator in emitted) {
      // Async generator case
      for await (const file of emitted) {
        count++
        if (ctx.argv.verbose) {
          console.log(`[emit:${emitter.name}] ${file}`)
        } else {
          log.updateText(`${emitter.name} -> ${styleText("gray", file)}`)
        }
      }
    } else {
      // Array case
      count += emitted.length
      for (const file of emitted) {
        if (ctx.argv.verbose) {
          console.log(`[emit:${emitter.name}] ${file}`)
        } else {
          log.updateText(`${emitter.name} -> ${styleText("gray", file)}`)
        }
      }
    }
  } catch (err) {
    trace(`Failed to emit from plugin \`${emitter.name}\``, err as Error)
  }
  return count
}

export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
  const { argv, cfg } = ctx
  const perf = new PerfTimer()
  const log = new QuartzLogger(ctx.argv.verbose)

  log.start(`Emitting files`)

  let emittedFiles = 0
  const staticResources = getStaticResourcesFromPlugins(ctx)

  // Phase 0: Run ComponentResources first so content-hashed asset filenames
  // (e.g. index-a3f2c1b.css) are available on ctx before pages are rendered.
  const componentResources = cfg.plugins.emitters.find((e) => e.name === "ComponentResources")
  if (componentResources) {
    emittedFiles += await runEmitter(componentResources, ctx, content, staticResources, log)
  }

  // Phase 1: Run PageTypeDispatcher so it populates ctx.virtualPages
  // with pages generated by page type plugins (tag pages, folder pages, bases pages, etc.)
  const dispatcher = cfg.plugins.emitters.find((e) => e.name === "PageTypeDispatcher")
  if (dispatcher) {
    ctx.virtualPages = []
    emittedFiles += await runEmitter(dispatcher, ctx, content, staticResources, log)
  }

  // Phase 2: Run all other emitters with content extended by virtual pages.
  // This ensures emitters like ContentIndex include virtual pages in their output
  // (e.g. sitemap, RSS, contentIndex.json used by the explorer sidebar).
  const contentWithVirtual =
    ctx.virtualPages.length > 0 ? [...content, ...ctx.virtualPages] : content
  const otherEmitters = cfg.plugins.emitters.filter(
    (e) => e.name !== "PageTypeDispatcher" && e.name !== "ComponentResources",
  )
  let emitErrors = 0
  const counts = await Promise.all(
    otherEmitters.map((emitter) =>
      runEmitter(emitter, ctx, contentWithVirtual, staticResources, log).catch((err) => {
        emitErrors++
        console.error(`Emitter "${emitter.name}" failed:`, err.message ?? err)
        return 0
      }),
    ),
  )
  emittedFiles += counts.reduce((sum, c) => sum + c, 0)

  if (emitErrors > 0) {
    console.warn(
      `\nBuild completed with ${emitErrors} emitter failure(s). Output may be incomplete.`,
    )
  }

  log.end(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince()}`)
}
