import fs from "node:fs"
import path from "node:path"

import { CONTENT_PACK_DEFINITIONS, SYSTEM_PACK_DEFINITIONS, buildPacks } from "./pack-builder.mjs"

const systemRoot = path.resolve(import.meta.dirname, "..")
const targetRoot = path.resolve(
  process.env.FVTT_LES_OUBLIES_BASE_ROOT || path.join(systemRoot, "..", "fvtt-les-oublies-base"),
)
const systemManifestPath = path.join(systemRoot, "system.json")
const systemPackagePath = path.join(systemRoot, "package.json")
const systemSourceRoot = path.join(systemRoot, "packs-src")
const targetSourceRoot = path.join(targetRoot, "packs-src")
const targetPacksRoot = path.join(targetRoot, "packs")
const moduleRepoUrl = "https://www.uberwald.me/gitea/public/fvtt-les-oublies-base"

const systemManifest = JSON.parse(fs.readFileSync(systemManifestPath, "utf8"))
const systemPackage = JSON.parse(fs.readFileSync(systemPackagePath, "utf8"))
const richFieldMap = Object.fromEntries(
  Object.entries(systemManifest.documentTypes?.Item ?? {}).map(([type, data]) => [type, data.htmlFields ?? []]),
)
const coreVersion = String(systemManifest.compatibility?.verified ?? systemManifest.compatibility?.minimum ?? "")
const basePackDefinitions = CONTENT_PACK_DEFINITIONS.map((pack) => ({
  ...pack,
  outputFolder: `base-${pack.outputFolder}`,
}))
const contentPackNames = new Set(CONTENT_PACK_DEFINITIONS.map((pack) => pack.outputFolder))

function setDeepValue(target, propertyPath, value) {
  const segments = String(propertyPath || "").split(".").filter(Boolean)
  if (!segments.length) return
  let cursor = target
  while (segments.length > 1) {
    const segment = segments.shift()
    if (!(segment in cursor) || typeof cursor[segment] !== "object" || cursor[segment] === null) {
      cursor[segment] = {}
    }
    cursor = cursor[segment]
  }
  cursor[segments[0]] = value
}

function sanitizeEntries(entries = []) {
  let clearedFields = 0
  const sanitized = entries.map((entry) => {
    const fields = richFieldMap[entry.type] ?? []
    if (!fields.length) return entry

    const clone = structuredClone(entry)
    clone.system ??= {}
    for (const fieldPath of fields) {
      setDeepValue(clone.system, fieldPath, "")
      clearedFields += 1
    }
    return clone
  })

  return { sanitized, clearedFields }
}

function countNonEmptyRichFields(entries = []) {
  let nonEmpty = 0
  for (const entry of entries) {
    for (const fieldPath of richFieldMap[entry.type] ?? []) {
      const value = fieldPath
        .split(".")
        .reduce((cursor, segment) => cursor?.[segment], entry.system ?? {})
      if ((value ?? "") !== "") nonEmpty += 1
    }
  }
  return nonEmpty
}

function parseJsonArray(rawText, filePath) {
  const parsed = JSON.parse(rawText)
  if (!Array.isArray(parsed)) {
    return {
      parsed,
      entries: null,
      isArray: false,
      error: `${filePath} must contain a JSON array`,
    }
  }

  return {
    parsed,
    entries: parsed,
    isArray: true,
    error: null,
  }
}

function ensureWritableTargetRoot() {
  const parentDir = path.dirname(targetRoot)
  fs.mkdirSync(parentDir, { recursive: true })
  fs.accessSync(parentDir, fs.constants.W_OK)
  fs.mkdirSync(targetRoot, { recursive: true })
  fs.accessSync(targetRoot, fs.constants.W_OK)
}

function ensureTargetModuleScaffold() {
  ensureWritableTargetRoot()
  fs.mkdirSync(targetSourceRoot, { recursive: true })
  fs.mkdirSync(targetPacksRoot, { recursive: true })

  const moduleManifestPath = path.join(targetRoot, "module.json")

  const moduleManifest = {
    id: "fvtt-les-oublies-base",
    title: "Les Oubliés Base",
    description: "Module de contenu pour Les Oubliés, conservant les compendiums complets avec leurs textes descriptifs.",
    manifest: `${moduleRepoUrl}/raw/branch/main/module.json`,
    download: "#{DOWNLOAD}#",
    url: moduleRepoUrl,
    version: systemPackage.version,
    authors: [
      {
        name: "Copilot",
        flags: {},
      },
    ],
    compatibility: systemManifest.compatibility,
    relationships: {
      requires: [
        {
          id: systemManifest.id,
          type: "system",
          compatibility: {
            minimum: systemManifest.compatibility?.minimum ?? undefined,
            verified: systemManifest.compatibility?.verified ?? undefined,
          },
        },
      ],
      systems: [
        {
          id: systemManifest.id,
          type: "system",
          compatibility: {
            minimum: systemManifest.compatibility?.minimum ?? undefined,
            verified: systemManifest.compatibility?.verified ?? undefined,
          },
        },
      ],
    },
    packs: (systemManifest.packs ?? []).map((pack) => ({
      // The base content module only mirrors content packs, not system journals or utility packs.
      ...pack,
    }))
      .filter((pack) => contentPackNames.has(pack.name))
      .map((pack) => ({
      ...pack,
      name: `base-${pack.name}`,
      path: `packs/base-${pack.name}`,
      system: systemManifest.id,
    })),
  }

  fs.writeFileSync(moduleManifestPath, `${JSON.stringify(moduleManifest, null, 2)}\n`)
}

function pruneStalePackDirectories(outputRoot, expectedDefinitions) {
  if (!fs.existsSync(outputRoot)) return
  const expected = new Set(expectedDefinitions.map((definition) => definition.outputFolder))
  for (const entry of fs.readdirSync(outputRoot, { withFileTypes: true })) {
    if (!entry.isDirectory()) continue
    if (expected.has(entry.name)) continue
    fs.rmSync(path.join(outputRoot, entry.name), { recursive: true, force: true })
  }
}

function copyAndSanitizeSources() {
  const summaries = []
  const sourceFiles = fs.readdirSync(systemSourceRoot)
    .filter((entry) => entry.endsWith(".json"))
    .sort((left, right) => left.localeCompare(right, "fr"))

  for (const fileName of sourceFiles) {
    const systemSourcePath = path.join(systemSourceRoot, fileName)
    const targetSourcePath = path.join(targetSourceRoot, fileName)
    const rawText = fs.readFileSync(systemSourcePath, "utf8")
    const systemJson = parseJsonArray(rawText, systemSourcePath)
    const targetRawText = fs.existsSync(targetSourcePath) ? fs.readFileSync(targetSourcePath, "utf8") : null
    const targetJson = targetRawText ? parseJsonArray(targetRawText, targetSourcePath) : null
    const systemRichCount = systemJson.isArray ? countNonEmptyRichFields(systemJson.entries) : -1
    const targetRichCount = targetJson?.isArray ? countNonEmptyRichFields(targetJson.entries) : -1
    const authoritativeRawText = targetRichCount > systemRichCount ? targetRawText : rawText
    const authoritativeJson = targetRichCount > systemRichCount && targetJson?.isArray ? targetJson : systemJson

    fs.writeFileSync(targetSourcePath, authoritativeRawText)

    if (!authoritativeJson.isArray) {
      summaries.push({ fileName, clearedFields: 0, copiedOnly: true })
      continue
    }

    const { sanitized, clearedFields } = sanitizeEntries(authoritativeJson.entries)
    fs.writeFileSync(systemSourcePath, `${JSON.stringify(sanitized, null, 2)}\n`)
    summaries.push({ fileName, clearedFields, copiedOnly: false })
  }

  return summaries
}

ensureTargetModuleScaffold()
const summaries = copyAndSanitizeSources()
pruneStalePackDirectories(targetPacksRoot, basePackDefinitions)

await buildPacks({
  sourceRoot: systemSourceRoot,
  outputRoot: path.join(systemRoot, "packs"),
  packDefinitions: SYSTEM_PACK_DEFINITIONS,
  documentSystemId: systemManifest.id,
  documentSystemVersion: systemPackage.version,
  coreVersion,
})

await buildPacks({
  sourceRoot: targetSourceRoot,
  outputRoot: targetPacksRoot,
  packDefinitions: basePackDefinitions,
  documentSystemId: systemManifest.id,
  documentSystemVersion: systemPackage.version,
  coreVersion,
})

console.info(`Base module root: ${targetRoot}`)

for (const summary of summaries) {
  if (summary.copiedOnly) {
    console.info(`${summary.fileName}: copied as-is`)
  } else {
    console.info(`${summary.fileName}: cleared ${summary.clearedFields} rich fields in system source`)
  }
}
