const fs = require('fs')
const path = require('path')

/** Default barrel path under the consumer site (Next.js app/ route). */
const DEFAULT_COMPONENTS_BARREL_REL = 'src/app/components/Components.tsx'

/** Import path in templates / cmsDependencies (tsconfig `paths` alias, usually @/) */
const DEFAULT_COMPONENTS_IMPORT_PATH = '@/app/components/Components'

/**
 * @param {string} sitePath
 * @returns {{ ok: true, abs: string } | { ok: false, abs: string, reason: string }}
 */
function sitePathMustBeDirectory(sitePath) {
  if (sitePath == null || String(sitePath).trim() === '') {
    return { ok: false, abs: '', reason: 'sitePath is empty' }
  }
  const abs = path.resolve(String(sitePath))
  if (!fs.existsSync(abs)) {
    return { ok: false, abs, reason: 'sitePath does not exist' }
  }
  if (!fs.statSync(abs).isDirectory()) {
    return {
      ok: false,
      abs,
      reason:
        'sitePath must be the consumer site root (a directory), not a file. ' +
        'If ~/.bild.json `sitePath` points at your cmsDependencies file or another file, set it to the project directory (e.g. where package.json lives), or pass --site-path explicitly.',
    }
  }
  return { ok: true, abs }
}

const ENT_HEADER = `// Copyright 2026, BILDIT, INC.
//
// Licensed under the ENT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://bildit.co/ENTLicense
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
`

/**
 * Re-export barrel for site components consumed by the CMS through cmsDependencies
 * (see getCmsDependenciesBoilerplate with withComponents: true).
 *
 */
function getComponentsBarrelBoilerplate() {
  return (
    ENT_HEADER +
    `'use client'
/* Shared UI barrel: re-export what you want the CMS to access via
 * import { … } from '${DEFAULT_COMPONENTS_IMPORT_PATH}'
 * and the '@components' key in src/cmsDependencies.ts. Adjust paths; add or remove
 * re-exports as your app grows. */

// Example layout — replace with your real files or add siblings under ./home, ./site-header, etc.
// export { Hero } from './Components/Hero'
`
  )
}

/**
 * @param {object} [opts]
 * @param {boolean} [opts.withComponents] - include import * as Components and '@components' registry entry
 * @param {string} [opts.componentsImportPath] - e.g. @/app/components/Components
 */
function getCmsDependenciesBoilerplate(opts = {}) {
  const withComponents = Boolean(opts.withComponents)
  const compPath = opts.componentsImportPath || DEFAULT_COMPONENTS_IMPORT_PATH
  const compPathQ = compPath.replace(/'/g, "\\'")
  const compImportBlock = withComponents
    ? `import * as Components from '${compPathQ}'
`
    : ''
  const compRegistryBlock = withComponents
    ? `  '@components': {
    module: Components
  },
  '${compPathQ}': {
    module: Components
  },
`
    : ''

  return `${ENT_HEADER}/* README:

  This file is used to define the dependencies for the CMS. These dependencies
  are used in the CMS to render components, pages, or banners using local modules. */
/* node_modules — React & Next only. */
'use client'
import * as React from 'react'
/* Local modules — import as a namespace, then add a matching cmsDependencies entry.
 *
 * Example:
 *
 *   import * as Example from '@/components/Example'
 *
 *   // In cmsDependencies:
 *   '@/components/Example': { module: Example },
 */
${compImportBlock}import { getRemoteProps } from '@bildit-platform/nextjs'
// EXAMPLE: import * as LucideReact from 'lucide-react'
import * as Next from 'next/client'
import * as NextImage from 'next/image'
import * as NextLink from 'next/link'
import * as ReactDOM from 'react-dom'
import * as ReactDOMClient from 'react-dom/client'
import * as JsxDevRuntime from 'react/jsx-dev-runtime'
import * as JsxRuntime from 'react/jsx-runtime'

interface Dependency {
  module: unknown
  globalName?: string
}

const cmsDependencies: Record<string, Dependency> = {
  'next/client': {
    module: Next
  },
  'next/image': {
    module: NextImage
  },
  'next/link': {
    module: NextLink
  },
  'react': {
    module: React
  },
  'react/jsx-runtime': {
    module: JsxRuntime
  },
  'react/jsx-dev-runtime': {
    module: JsxDevRuntime
  },
  'react-dom': {
    module: ReactDOM
  },
  'react-dom/client': {
    module: ReactDOMClient
  }, // *** EXAMPLE EXTERNAL LIBRARY ***
//  'lucide-react': {
//    module: LucideReact,
//    globalName: 'lucideReact'
//  },
${compRegistryBlock}}

export default cmsDependencies
declare global {
  interface Window {
    cmsDependencies: Record<string, unknown>
    React: typeof React
    ReactDOM: typeof ReactDOM & typeof ReactDOMClient
    remoteProps: typeof getRemoteProps
    // EXAMPLE: 'lucide-react': typeof import('lucide-react')
  }
}

if (typeof window !== 'undefined') {
  window.React = React
  window.ReactDOM = { ...ReactDOM, ...ReactDOMClient } as typeof window.ReactDOM
  window.cmsDependencies = cmsDependencies as unknown as Record<string, unknown>
  window.remoteProps = getRemoteProps
}
`
}

/**
 * Write the Components re-export barrel (e.g. src/app/components/Components.tsx).
 */
function writeComponentsBarrelBoilerplate(sitePath, relativeBarrelFile, opts = {}) {
  const force = Boolean(opts.force)
  const useJsx = Boolean(opts.useJsx)
  const dir = sitePathMustBeDirectory(sitePath)
  if (!dir.ok) {
    return { ok: false, path: dir.abs, reason: dir.reason }
  }
  const absSite = dir.abs
  const rel = relativeBarrelFile || (useJsx ? 'src/app/components/Components.jsx' : DEFAULT_COMPONENTS_BARREL_REL)
  const absFile = path.resolve(absSite, rel)
  if (fs.existsSync(absFile) && !force) {
    return { ok: true, skipped: true, path: absFile }
  }
  fs.mkdirSync(path.dirname(absFile), { recursive: true })
  fs.writeFileSync(absFile, getComponentsBarrelBoilerplate(), 'utf8')
  return { ok: true, skipped: false, path: absFile }
}

/**
 * @param {string} sitePath
 * @param {string} relativeDepsFile
 * @param {{ force?: boolean, withComponents?: boolean, componentsBarrelRel?: string, componentsImportPath?: string, createComponentsBarrel?: boolean, useJsxBarrel?: boolean }} [opts]
 */
function writeCmsDependenciesBoilerplate(sitePath, relativeDepsFile, opts = {}) {
  const force = Boolean(opts.force)
  const barrelRel =
    opts.componentsBarrelRel || (opts.useJsxBarrel ? 'src/app/components/Components.jsx' : DEFAULT_COMPONENTS_BARREL_REL)
  const dir = sitePathMustBeDirectory(sitePath)
  if (!dir.ok) {
    return { ok: false, path: dir.abs || path.resolve(String(sitePath), relativeDepsFile), reason: dir.reason }
  }
  const absSite = dir.abs
  const absBarrel = path.resolve(absSite, barrelRel)
  const createBarrel = opts.createComponentsBarrel !== false
  let withComponents
  if (opts.withComponents === true) {
    withComponents = true
  } else if (opts.withComponents === false) {
    withComponents = false
  } else {
    withComponents = fs.existsSync(absBarrel)
  }
  const absFile = path.resolve(absSite, relativeDepsFile)
  if (fs.existsSync(absFile) && !force) {
    return { ok: true, skipped: true, path: absFile, withComponents: false }
  }
  if (withComponents && createBarrel) {
    const w = writeComponentsBarrelBoilerplate(sitePath, barrelRel, { force: false, useJsx: Boolean(opts.useJsxBarrel) })
    if (!w.ok) {
      return w
    }
  }
  const text = getCmsDependenciesBoilerplate({
    withComponents,
    componentsImportPath: opts.componentsImportPath,
  })
  fs.mkdirSync(path.dirname(absFile), { recursive: true })
  fs.writeFileSync(absFile, text, 'utf8')
  return { ok: true, skipped: false, path: absFile, withComponents }
}

/**
 * @param {string} filePath
 * @param {{ force?: boolean, withComponents?: boolean, componentsImportPath?: string, createComponentsBarrel?: boolean, sitePathForBarrel?: string, componentsBarrelRel?: string, useJsxBarrel?: boolean }} [opts]
 */
function writeCmsDependenciesBoilerplateToFilePath(filePath, opts = {}) {
  const force = Boolean(opts.force)
  const createBarrel = opts.createComponentsBarrel !== false
  const barrelRel =
    opts.componentsBarrelRel || (opts.useJsxBarrel ? 'src/app/components/Components.jsx' : DEFAULT_COMPONENTS_BARREL_REL)
  const siteForBarrel = opts.sitePathForBarrel
  const absBarrel = siteForBarrel ? path.resolve(siteForBarrel, barrelRel) : null
  let withComponents
  if (opts.withComponents === true) {
    withComponents = true
  } else if (opts.withComponents === false) {
    withComponents = false
  } else {
    withComponents = Boolean(absBarrel && fs.existsSync(absBarrel))
  }
  const absFile = path.resolve(filePath)
  if (fs.existsSync(absFile) && !force) {
    return { ok: true, skipped: true, path: absFile }
  }
  if (withComponents && createBarrel && siteForBarrel) {
    const w = writeComponentsBarrelBoilerplate(siteForBarrel, barrelRel, { force: false, useJsx: Boolean(opts.useJsxBarrel) })
    if (!w.ok) {
      return w
    }
  }
  fs.mkdirSync(path.dirname(absFile), { recursive: true })
  const text = getCmsDependenciesBoilerplate({
    withComponents,
    componentsImportPath: opts.componentsImportPath,
  })
  fs.writeFileSync(absFile, text, 'utf8')
  return { ok: true, skipped: false, path: absFile, withComponents }
}

/**
 * Resolve default on-disk path for the components barrel and the `import` string used in cmsDependencies.
 * Prefers an existing .tsx, then .jsx, else defaults to .tsx.
 *
 * @param {string} sitePathAbs
 * @returns {{ barrelAbs: string, componentsImportPath: string, useJsx: boolean }}
 */
function resolveDefaultComponentsBarrelOnDisk(sitePathAbs) {
  // Prefer `app/components/…` (App Router at repo root) when present, then `src/app/…`.
  const underApp = path.join(sitePathAbs, 'app', 'components', 'Components')
  const underSrc = path.join(sitePathAbs, 'src', 'app', 'components', 'Components')
  const bases = [underApp, underSrc]
  for (const base of bases) {
    if (fs.existsSync(`${base}.tsx`)) {
      return { barrelAbs: `${base}.tsx`, componentsImportPath: DEFAULT_COMPONENTS_IMPORT_PATH, useJsx: false }
    }
    if (fs.existsSync(`${base}.jsx`)) {
      return { barrelAbs: `${base}.jsx`, componentsImportPath: DEFAULT_COMPONENTS_IMPORT_PATH, useJsx: true }
    }
  }
  if (fs.existsSync(path.join(sitePathAbs, 'app'))) {
    return { barrelAbs: `${underApp}.tsx`, componentsImportPath: DEFAULT_COMPONENTS_IMPORT_PATH, useJsx: false }
  }
  return { barrelAbs: `${underSrc}.tsx`, componentsImportPath: DEFAULT_COMPONENTS_IMPORT_PATH, useJsx: false }
}

/**
 * Idempotently add `export { … } from '…'` lines to the components barrel; creates the file from boilerplate if missing.
 *
 * @param {string} barrelFileAbs
 * @param {string[]} exportLines
 * @returns {{ ok: boolean, added?: number, reason?: string }}
 */
function mergeBarrelReexportLines(barrelFileAbs, exportLines) {
  if (!barrelFileAbs || !exportLines || !exportLines.length) {
    return { ok: true, added: 0 }
  }
  const normalized = exportLines
    .map((l) => String(l).trim())
    .filter(Boolean)
  if (!normalized.length) {
    return { ok: true, added: 0 }
  }
  let text = ''
  if (fs.existsSync(barrelFileAbs)) {
    text = fs.readFileSync(barrelFileAbs, 'utf8')
  } else {
    fs.mkdirSync(path.dirname(barrelFileAbs), { recursive: true })
    text = getComponentsBarrelBoilerplate()
  }
  const existing = new Set(
    text
      .split('\n')
      .map((l) => l.trim())
      .filter((l) => l.startsWith('export ')),
  )
  const toAdd = []
  for (const line of normalized) {
    if (existing.has(line)) {
      continue
    }
    if (line.includes(" from '") || line.includes(' from "')) {
      const fromMatch = line.match(/ from\s*['"]([^'"]+)['"]/)
      if (fromMatch) {
        const reExp = new RegExp(
          'export\\s*\\{[^}]*\\}\\s*from\\s*[\'"]' + fromMatch[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[\'"]',
        )
        if (reExp.test(text)) {
          continue
        }
      }
    }
    toAdd.push(line)
    existing.add(line)
  }
  if (toAdd.length === 0) {
    return { ok: true, added: 0 }
  }
  const sep = text.endsWith('\n') ? '' : '\n'
  const block = toAdd.map((l) => (l.endsWith(';') ? l : l)).join('\n') + '\n'
  fs.writeFileSync(barrelFileAbs, text + sep + block, 'utf8')
  return { ok: true, added: toAdd.length }
}

module.exports = {
  DEFAULT_COMPONENTS_BARREL_REL,
  DEFAULT_COMPONENTS_IMPORT_PATH,
  getCmsDependenciesBoilerplate,
  getComponentsBarrelBoilerplate,
  mergeBarrelReexportLines,
  resolveDefaultComponentsBarrelOnDisk,
  sitePathMustBeDirectory,
  writeCmsDependenciesBoilerplate,
  writeCmsDependenciesBoilerplateToFilePath,
  writeComponentsBarrelBoilerplate,
}
