const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const chalk = require('chalk')
const ora = require('ora')
const { getConfig } = require('../lib/config')
const { createCodeLibrary, listCodeLibraries } = require('../lib/api')
const webTranspile = require('../lib/transpile')
const resolveWebCodeType = webTranspile.resolveWebCodeType
const { lint } = require('../lib/lint')
const { resolveImageUrl, resolveLocalImagePath } = require('../lib/media')
const {
  preprocessBilditTemplate,
  canonicalizeDoubleParenAsCastTemplates,
} = require('../lib/preprocessBilditTemplate')
const {
  analyzeLibraryDeps,
  printLibraryDepsHints,
  analysisHasCmsDepsWork,
  needsOptionalDepsFilePrompt,
} = require('../lib/library-deps-hint')
const { applyCmsDependenciesForCodeLibraryIfEnabled } = require('./library-apply-deps')
const { codeLibraryHasDefaultExport, NO_DEFAULT_EXPORT } = require('../lib/ensure-code-library-default-export')
const {
  extractTemplateIdFromPath,
  assertFileIsBilditWebTemplate,
  describeBilditTemplateKindForLog,
  shouldDefaultNoLintLibraryAdd,
} = require('../lib/template-meta')

function detectCodeType(filePath) {
  const ext = path.extname(filePath).toLowerCase()
  const extMap = {
    '.js': 'js',
    '.jsx': 'jsx',
    '.ts': 'ts',
    '.tsx': 'tsx',
    '.html': 'html',
  }
  return extMap[ext] || 'jsx'
}

async function findExistingCodeLibrary(appId, templateId) {
  try {
    const templateLibs = await listCodeLibraries(appId)
    const items = Array.isArray(templateLibs) ? templateLibs : Object.values(templateLibs || {})
    return items.find((lib) => lib.templateId === templateId || lib.snippetId === templateId)
  } catch (error) {
    // Do not swallow auth errors: otherwise we run lint/transpile/image upload and fail late on create,
    // and the user only sees a generic "keychain" message. Fail fast on the first CMS call.
    if (error && error.name === 'APIError' && (error.status === 401 || error.status === 403)) {
      throw error
    }
    return null
  }
}

async function addFile(filePath, options) {
  const spinner = ora({ text: `Processing ${path.basename(filePath)}`, stream: process.stderr }).start()

  try {
    // Read file
    const code = fs.readFileSync(filePath, 'utf8')
    let codeType = resolveWebCodeType(code, options.codeType || detectCodeType(filePath))
    const templateId = options.templateId || extractTemplateIdFromPath(filePath)
    const appId = options.appId || getConfig().defaultAppId

    if (!appId) {
      spinner.fail('App ID is required')
      throw new Error('App ID is required. Use --app-id or set default in config.')
    }

    if (!templateId) {
      spinner.fail('Template ID is required')
      throw new Error('Template ID is required.')
    }

    // Check if already exists
    const existing = await findExistingCodeLibrary(appId, templateId)
    if (existing) {
      spinner.fail(`A template with ID "${templateId}" already exists. Use "library update" instead.`)
      throw new Error(`Template with ID "${templateId}" already exists`)
    }

    // Same deps analysis as template legacy commands: show cmsDependencies additions & moved imports.
    let depsAnalysis = null
    try {
      depsAnalysis = analyzeLibraryDeps(filePath, {
        depsFile: options.depsFile,
        sitePath: options.sitePath,
        depsImportAlias: options.depsAlias,
      })
    } catch (e) {
      console.warn(chalk.dim(`Could not analyze imports for cmsDependencies hints: ${e.message}`))
    }
    if (depsAnalysis) {
      spinner.stop()
      printLibraryDepsHints(depsAnalysis, filePath)
      spinner.start(`Processing ${path.basename(filePath)}`)
    }

    let uploadCode = code
    if (options.applyDeps) {
      spinner.text = 'Applying cmsDependencies import rewrites...'
      try {
        uploadCode = await applyCmsDependenciesForCodeLibraryIfEnabled(filePath, code, options, { label: 'library add' })
      } catch (e) {
        spinner.fail(`cmsDependencies rewrite failed: ${e.message}`)
        throw e
      }
    }

    try {
      assertFileIsBilditWebTemplate(uploadCode, codeType)
    } catch (e) {
      spinner.fail(e.message)
      throw e
    }
    // ora renders `spinner.text` on the active terminal line; stop to avoid concatenating log output.
    const resumeText = spinner.text
    spinner.stop()
    console.log(chalk.dim(describeBilditTemplateKindForLog(uploadCode, codeType)))
    spinner.start(resumeText)

    uploadCode = canonicalizeDoubleParenAsCastTemplates(uploadCode, codeType)

    if (!codeLibraryHasDefaultExport(uploadCode, codeType)) {
      spinner.fail(NO_DEFAULT_EXPORT)
      const err = new Error(NO_DEFAULT_EXPORT)
      err.code = 'BILD_NO_DEFAULT_EXPORT'
      throw err
    }

    // Preprocess BILDIT template vars ($(varName:Type="default")) so code can be linted and transpiled
    const codeForValidation = preprocessBilditTemplate(uploadCode, codeType)

    const runLint = options.lint !== false && !shouldDefaultNoLintLibraryAdd(filePath, uploadCode)
    if (runLint) {
      spinner.text = 'Linting code...'
      const lintResult = await lint(codeForValidation, filePath, { lintEnv: options.lintEnv })
      if (lintResult.errorCount > 0) {
        spinner.fail(`Lint failed with ${lintResult.errorCount} error(s). Fix issues before uploading.`)
        lintResult.issues
          .filter((i) => i.severity === 'error')
          .forEach((issue) => {
            console.log(chalk.red(`  Line ${issue.line}: ${issue.message}`))
          })
        throw new Error('Lint failed. Template was not created.')
      }
      if (lintResult.warningCount > 0) {
        spinner.warn(`Found ${lintResult.warningCount} lint warning(s)`)
        lintResult.issues
          .filter((i) => i.severity === 'warning')
          .slice(0, 5)
          .forEach((issue) => {
            console.log(chalk.yellow(`  Line ${issue.line}: ${issue.message}`))
          })
      }
    }

    // Transpile code (validates that preprocessed code compiles)
    spinner.text = 'Transpiling code...'
    void webTranspile(codeForValidation, codeType, templateId)

    // Get image URL (required) - can be URL or local file path
    const imageInput = options.image
    if (!imageInput) {
      spinner.fail('Image URL or file path is required. Use --image <url|path> or provide it when prompted.')
      throw new Error('Image URL or file path is required. Use --image <url|path> option.')
    }

    // Resolve image URL (upload local files to CMS when possible, else data URL)
    spinner.text = 'Processing image...'
    let imageUrl
    try {
      imageUrl = await resolveImageUrl(imageInput, appId)
    } catch (error) {
      spinner.fail(`Failed to process image: ${error.message}`)
      throw error
    }

    // Create template in CMS
    spinner.text = 'Creating template...'
    const codeLibraryData = {
      name: options.name || templateId,
      description: options.description || '',
      snippetId: templateId, // API expects snippetId, but CLI uses template-id
      type: options.type || 'Homepage',
      codeType,
      code: uploadCode,
      appType: 'web', // Default to web for React/Next.js
      image: imageUrl,
      isCustom: true,
    }

    const result = await createCodeLibrary(appId, codeLibraryData)
    const cid = result?.id ?? result

    spinner.succeed(`Successfully added ${path.basename(filePath)}`)
    return { cid, templateId }
  } catch (error) {
    spinner.fail(`Failed to add ${path.basename(filePath)}: ${error.message}`)
    throw error
  }
}

async function addCommand(files, options) {
  try {
    const config = getConfig()
    const prompt = inquirer.createPromptModule()
    
    // Prompt for app ID if not provided
    let appId = options.appId || config.defaultAppId
    if (!appId) {
      const { appId: promptedAppId } = await prompt([
        {
          type: 'input',
          name: 'appId',
          message: 'App ID:',
          validate: (input) => !!input || 'App ID is required',
        },
      ])
      appId = promptedAppId
    }

    const filePaths = []
    
    // If files provided, expand folders to files
    if (files && files.length > 0) {
      for (const file of files) {
        const resolvedPath = path.resolve(file)
        const stat = fs.statSync(resolvedPath)
        
        if (stat.isDirectory()) {
          const dirFiles = fs.readdirSync(resolvedPath)
          dirFiles.forEach((f) => {
            const fullPath = path.join(resolvedPath, f)
            if (fs.statSync(fullPath).isFile() && /\.(js|jsx|ts|tsx|html)$/.test(f)) {
              filePaths.push(fullPath)
            }
          })
        } else {
          filePaths.push(resolvedPath)
        }
      }
    }

    // If no files provided, prompt for file
    if (filePaths.length === 0) {
      const { filePath } = await prompt([
        {
          type: 'input',
          name: 'filePath',
          message: 'Enter file path:',
          validate: (input) => {
            if (!input) return 'File path is required'
            const resolved = path.resolve(input)
            if (!fs.existsSync(resolved)) return 'File does not exist'
            if (!fs.statSync(resolved).isFile()) return 'Path is not a file'
            return true
          },
        },
      ])
      filePaths.push(path.resolve(filePath))
    }

    console.log(chalk.blue(`\nProcessing ${filePaths.length} file(s)...\n`))

    // Process each file with prompts
    for (const filePath of filePaths) {
      const detectedCodeType = detectCodeType(filePath)
      const extractedTemplateId = extractTemplateIdFromPath(filePath)

      let depsPreview = null
      try {
        depsPreview = analyzeLibraryDeps(filePath, {
          depsFile: options.depsFile,
          sitePath: options.sitePath || config.sitePath,
          depsImportAlias: options.depsAlias,
        })
      } catch {
        depsPreview = null
      }

      // Prompt for missing information
      const answers = await prompt([
            {
              type: 'input',
              name: 'templateId',
              message: 'Template ID:',
              default: options.templateId || extractedTemplateId,
              when: !options.templateId,
              validate: (input) => !!input || 'Template ID is required',
            },
        {
          type: 'input',
          name: 'name',
          message: 'Display name:',
              default: options.name || extractedTemplateId,
          when: options.name === undefined,
        },
        {
          type: 'list',
          name: 'codeType',
          message: 'Code type:',
          choices: ['jsx', 'tsx', 'html'],
          default: options.codeType || (detectedCodeType === 'tsx' ? 'tsx' : detectedCodeType === 'html' ? 'html' : 'jsx'),
          when: !options.codeType,
        },
        {
          type: 'input',
          name: 'type',
          message: 'Template type:',
          default: options.type || 'Homepage',
          when: !options.type,
        },
        {
          type: 'input',
          name: 'description',
          message: 'Description:',
          default: options.description || '',
          when: options.description === undefined,
        },
        {
          type: 'input',
          name: 'image',
          message: 'Image URL or file path:',
          default: options.image || '',
          when: !options.image,
          validate: (input) => {
            if (!input) return 'Image URL or file path is required'
            // Check if it's a URL
            try {
              new URL(input)
              return true
            } catch {
              // Check if it's a data URL
              if (input.startsWith('data:image/')) return true
              // Local file (handles macOS screenshot names with U+202F, shell-escaped spaces)
              if (resolveLocalImagePath(input)) {
                return true
              }
              return 'Please enter a valid URL, data URL, or existing file path'
            }
          },
        },
        {
          type: 'input',
          name: 'depsFile',
          message:
            'cmsDependencies file path (used for import hints; with confirmation below, also updated when applying rewrites):',
          default: '',
          when: () => needsOptionalDepsFilePrompt(depsPreview) && !options.depsFile,
          filter: (input) => (input && String(input).trim()) || '',
        },
        {
          type: 'confirm',
          name: 'applyDepsRewrite',
          message:
            'Rewrite imports for cmsDependencies (record + namespace imports for external packages), update that file, and upload the rewritten code?',
          default: true,
          when: (answers) => {
            if (options.applyDeps) return false
            if (!analysisHasCmsDepsWork(depsPreview)) return false
            const hasDepsPath = Boolean(
              (options.depsFile && String(options.depsFile).trim()) ||
                (depsPreview && depsPreview.depsFilePath) ||
                (answers.depsFile && String(answers.depsFile).trim()),
            )
            if (!hasDepsPath) return false
            if (needsOptionalDepsFilePrompt(depsPreview) && !options.depsFile) {
              return !!(answers.depsFile && String(answers.depsFile).trim())
            }
            return true
          },
        },
      ])

      const promptedDeps = answers.depsFile && answers.depsFile.trim()
      const applyDeps = !!options.applyDeps || answers.applyDepsRewrite === true
      const finalOptions = {
        ...options,
        appId,
        templateId: options.templateId || answers.templateId,
        name: options.name !== undefined ? options.name : (answers.name || extractedTemplateId),
        codeType: options.codeType || answers.codeType,
        type: options.type || answers.type,
        description: options.description !== undefined ? options.description : answers.description,
        image: options.image || answers.image,
        depsFile: options.depsFile || promptedDeps || undefined,
        sitePath: options.sitePath || config.sitePath,
        depsAlias: options.depsAlias,
        applyDeps,
      }

      await addFile(filePath, finalOptions)
    }

    console.log(chalk.green('\n✓ All files added successfully!'))
  } catch (error) {
    console.error(chalk.red(`\n✗ Add failed: ${error.message}`))
    process.exit(1)
  }
}

module.exports = addCommand

