#!/usr/bin/env node
/**
 * cli:scaffold-component — Generate React page components from the
 * customisation-ui baseline templates.
 *
 * Output location options (precedence order):
 *   1. --pages-dir <abs_or_rel>   Absolute location of the pages folder.
 *                                 Overrides the default layout entirely.
 *                                 Example: --pages-dir src/pages/crm/prospection/leads
 *                                 → pages land at src/pages/crm/prospection/leads/LeadListPage.tsx
 *                                 (i18n files still land at web/<app>-web/src/i18n/…)
 *   2. --outdir <root>            Project root to write into. Pages go to the
 *                                 default feature layout
 *                                 (src/features/{module}/{entity}/pages/).
 *   3. (spec.projectPath)         Fallback when neither flag is set.
 *
 * The --pages-dir flag is the regeneration-mode input: ui-polish's regenerate
 * pass passes the existing page's directory so the new conformant file
 * OVERWRITES the old one in-place, regardless of project layout convention.
 */
import { parseArgs } from 'node:util'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, resolve, join, isAbsolute } from 'node:path'
import { validate } from './validate.js'
import { generate } from './generate.js'
import { generateEnvelope, failGenerate, printEnvelope } from '../../../../../lib/output.js'

/**
 * Deep merge two JSON-serialisable objects. Used to preserve any
 * keys already present in an existing i18n catalogue when the
 * scaffolder regenerates a page — multiple entities under the same
 * module share one `<module>.json` file, and we never want to wipe
 * sibling keys (forms, custom helpers, hand-edited copy) on a
 * targeted regen.
 *
 * Right-hand side (`patch`) wins on conflict at any leaf so the
 * fresh pageSpec content overwrites stale fragments while novel
 * branches stay intact.
 */
function deepMergeJson(base: unknown, patch: unknown): unknown {
  if (base === null || base === undefined) return patch
  if (patch === null || patch === undefined) return base
  if (typeof base !== 'object' || typeof patch !== 'object') return patch
  if (Array.isArray(base) || Array.isArray(patch)) return patch
  const out: Record<string, unknown> = { ...(base as Record<string, unknown>) }
  for (const [k, v] of Object.entries(patch as Record<string, unknown>)) {
    out[k] = deepMergeJson(out[k], v)
  }
  return out
}

const COMMAND = 'scaffold-component'

function main(): void {
  const { values } = parseArgs({
    options: {
      spec: { type: 'string' },
      outdir: { type: 'string' },
      'pages-dir': { type: 'string' },
      overwrite: { type: 'boolean', default: false },
      dry_run: { type: 'boolean', default: false },
    },
    strict: true,
  })

  if (!values.spec) {
    printEnvelope(failGenerate(COMMAND, ['--spec is required']))
    process.exit(1)
  }

  let raw: unknown
  try {
    raw = JSON.parse(values.spec)
  } catch {
    printEnvelope(failGenerate(COMMAND, ['Invalid JSON']))
    process.exit(1)
  }

  const validation = validate(raw)
  if (!validation.valid) {
    printEnvelope(failGenerate(COMMAND, validation.errors))
    process.exit(1)
  }

  const spec = raw as any
  const files = generate(spec)

  // --pages-dir rewrites page output paths (relative to spec.projectPath).
  // i18n files keep their default layout.
  const pagesDirArg = values['pages-dir'] as string | undefined
  const effectiveFiles = pagesDirArg
    ? files.map((f) => {
        if (/\.tsx$/i.test(f.path) && f.path.includes('/pages/')) {
          const basename = f.path.split('/').pop()!
          const absTarget = isAbsolute(pagesDirArg)
            ? join(pagesDirArg, basename)
            : join(pagesDirArg, basename)
          return { ...f, path: absTarget }
        }
        return f
      })
    : files

  if (values.dry_run) {
    printEnvelope(generateEnvelope(COMMAND, {
      data: { dryRun: true, files: effectiveFiles.map(f => f.path), pagesDirArg: pagesDirArg ?? null },
    }))
    process.exit(0)
  }

  const outdir = values.outdir ?? spec.projectPath
  const written: string[] = []

  for (const file of effectiveFiles) {
    // When --pages-dir is ABSOLUTE, skip the outdir join.
    const p = isAbsolute(file.path)
      ? resolve(file.path)
      : resolve(join(outdir, file.path))
    mkdirSync(dirname(p), { recursive: true })
    // i18n locale files at /src/i18n/locales/<locale>/<module>.json are
    // module-level — multiple entities under the same module share the
    // file. Read existing JSON, deep-merge with the freshly generated
    // payload, write back. This way scaffolding Budget never wipes
    // sibling keys belonging to BudgetAlert / common / hand-edited copy.
    const isI18nLocaleJson = /[\\\/]i18n[\\\/]locales[\\\/][^\\\/]+[\\\/][^\\\/]+\.json$/i.test(p)
    if (isI18nLocaleJson && existsSync(p)) {
      try {
        const existing = JSON.parse(readFileSync(p, 'utf-8')) as unknown
        const incoming = JSON.parse(file.content) as unknown
        const merged = deepMergeJson(existing, incoming)
        writeFileSync(p, JSON.stringify(merged, null, 2) + '\n', 'utf-8')
        written.push(p)
        continue
      } catch {
        // Fallback: existing file is malformed JSON — overwrite with new.
      }
    }
    writeFileSync(p, file.content, 'utf-8')
    written.push(p)
  }

  printEnvelope(generateEnvelope(COMMAND, {
    data: {
      entity: spec.entity,
      views: spec.views,
      fileCount: written.length,
      overwrite: values.overwrite ?? false,
      pagesDir: pagesDirArg ?? null,
    },
    filesCreated: written,
    nextSteps: ['Run npm run build'],
  }))
}

main()
