import type { ScriptParameter, ParameterCandidate } from '@/types'

/**
 * PowerShell variable names to skip — these are PS internals, not user inputs.
 */
const SKIP_VARIABLES = new Set([
  '$ErrorActionPreference',
  '$WarningPreference',
  '$VerbosePreference',
  '$DebugPreference',
  '$InformationPreference',
  '$ConfirmPreference',
  '$ProgressPreference',
  '$PSDefaultParameterValues',
  '$PSModuleAutoLoadingPreference',
  '$OFS',
  '$FormatEnumerationLimit',
  '$MaximumHistoryCount',
  '$_',
  '$PSItem',
  '$args',
  '$input',
  '$this',
  '$null',
  '$true',
  '$false',
])

/**
 * Sensitive variable name patterns — if the variable name contains any of these,
 * suggest password type and mark sensitive.
 */
const SENSITIVE_PATTERNS = /password|secret|key|credential|token|apikey|api_key/i

/**
 * Convert a PowerShell variable name to a snake_case key.
 * "$OUPath" → "ou_path", "$ServerName" → "server_name"
 */
function toSnakeCase(varName: string): string {
  const name = varName.replace(/^\$/, '')
  return name
    .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
    .toLowerCase()
}

/**
 * Convert a snake_case key to a human-readable label.
 * "ou_path" → "OU Path", "server_name" → "Server Name"
 */
function toLabel(key: string): string {
  return key
    .split('_')
    .map(w => w.charAt(0).toUpperCase() + w.slice(1))
    .join(' ')
}

/**
 * Infer the ScriptParameter type from a PowerShell type annotation and/or value.
 */
function inferType(
  typeAnnotation: string | null,
  value: string | null,
  varName: string
): { type: ScriptParameter['type']; sensitive: boolean; reason: string } {
  if (typeAnnotation) {
    const t = typeAnnotation.toLowerCase()
    if (t === 'switch') {
      return { type: 'boolean', sensitive: false, reason: 'Detected [switch] type declaration' }
    }
    if (t === 'securestring') {
      return { type: 'password', sensitive: true, reason: 'Detected [SecureString] type — marked as sensitive' }
    }
    if (t === 'int' || t === 'int32' || t === 'int64' || t === 'double' || t === 'float' || t === 'decimal') {
      return { type: 'number', sensitive: false, reason: `Detected [${typeAnnotation}] type declaration` }
    }
    if (t === 'bool' || t === 'boolean') {
      return { type: 'boolean', sensitive: false, reason: `Detected [${typeAnnotation}] type declaration` }
    }
  }

  if (SENSITIVE_PATTERNS.test(varName)) {
    return { type: 'password', sensitive: true, reason: 'Variable name suggests sensitive data — marked as sensitive' }
  }

  if (value !== null) {
    const trimmed = value.trim()
    if (trimmed === '$true' || trimmed === '$false') {
      return { type: 'boolean', sensitive: false, reason: 'Detected boolean value ($true/$false)' }
    }
    if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
      return { type: 'number', sensitive: false, reason: 'Detected numeric value' }
    }
  }

  const reason = typeAnnotation
    ? `Detected [${typeAnnotation}] type declaration`
    : 'Defaulting to text (no type annotation detected)'
  return { type: 'text', sensitive: false, reason }
}

/**
 * Parse the default value into the correct JS type.
 */
function parseDefault(value: string | null, type: ScriptParameter['type']): string | boolean | number | null {
  if (value === null) return null
  const trimmed = value.trim()

  if (type === 'boolean') {
    if (trimmed === '$true') return true
    if (trimmed === '$false') return false
    return null
  }
  if (type === 'number') {
    const n = Number(trimmed)
    return isNaN(n) ? null : n
  }
  if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
      (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
    return trimmed.slice(1, -1)
  }
  return trimmed
}

/**
 * Find the script-level param() block (not inside any function).
 */
function findScriptLevelParamBlock(script: string): { start: number; end: number } | null {
  const lines = script.split('\n')
  let functionBraceDepth = 0
  let paramStart = -1
  let parenDepth = 0

  for (let i = 0; i < lines.length; i++) {
    const trimmed = lines[i].trim()

    // Track function blocks with brace depth to handle nested braces
    if (/^function\s+/i.test(trimmed)) {
      // Count braces on this line and subsequent lines
      for (const ch of lines[i]) {
        if (ch === '{') functionBraceDepth++
        if (ch === '}') functionBraceDepth--
      }
      continue
    }

    // Track braces while inside a function
    if (functionBraceDepth > 0) {
      for (const ch of lines[i]) {
        if (ch === '{') functionBraceDepth++
        if (ch === '}') functionBraceDepth--
      }
      continue
    }

    if (functionBraceDepth === 0 && /^param\s*\(/i.test(trimmed) && paramStart === -1) {
      paramStart = i
      let foundOpen = false
      for (let j = i; j < lines.length; j++) {
        for (const ch of lines[j]) {
          if (ch === '(') { parenDepth++; foundOpen = true }
          if (ch === ')') parenDepth--
          if (foundOpen && parenDepth === 0) {
            return { start: paramStart, end: j }
          }
        }
      }
    }
  }

  return null
}

/**
 * Extract parameter candidates from a script-level param() block.
 */
function extractParamBlockCandidates(
  script: string,
  block: { start: number; end: number }
): ParameterCandidate[] {
  const lines = script.split('\n')
  const candidates: ParameterCandidate[] = []

  // Scan each line in the param block for $VarName patterns.
  // Lines with [Parameter(...)], [ValidateSet(...)], etc. don't contain $VarName
  // so they are naturally skipped. The type annotation [string], [int], etc.
  // appears on the same line as $VarName.
  const varLineRegex = /(?:\[(\w+)\])?\s*\$(\w+)(?:\s*=\s*(.+?))?(?:\s*,?\s*$)/

  for (let i = block.start; i <= block.end; i++) {
    const trimmed = lines[i].trim()

    // Skip attribute lines like [Parameter(...)], [ValidateSet(...)], etc.
    if (/^\[(?:Parameter|ValidateSet|ValidateRange|ValidatePattern|ValidateScript|ValidateLength|ValidateCount|Alias|AllowNull|AllowEmptyString|AllowEmptyCollection)\s*\(/i.test(trimmed)) {
      continue
    }

    const match = trimmed.match(varLineRegex)
    if (!match) continue

    const typeAnnotation = match[1] || null
    const varName = match[2]
    const rawDefault = match[3]?.trim() ?? null

    // Skip if type looks like an attribute we didn't catch above
    if (typeAnnotation && /^Parameter$/i.test(typeAnnotation)) continue

    const key = toSnakeCase(varName)
    const { type, sensitive, reason } = inferType(typeAnnotation, rawDefault, varName)
    const defaultValue = parseDefault(rawDefault, type)

    candidates.push({
      variableName: `$${varName}`,
      suggestedKey: key,
      suggestedLabel: toLabel(key),
      suggestedType: type,
      sensitive,
      defaultValue,
      source: 'param_block',
      lineNumber: i + 1,
      matchedLine: trimmed,
      inferenceReason: reason,
    })
  }

  return candidates
}

/**
 * Extract parameter candidates from variable assignments ($Var = 'value').
 */
function extractAssignmentCandidates(
  script: string,
  existingVarNames: Set<string>
): ParameterCandidate[] {
  const lines = script.split('\n')
  const candidates: ParameterCandidate[] = []
  const seenVars = new Set<string>()

  const assignRegex = /^\s*(\$\w+)\s*=\s*(.+)$/

  for (let i = 0; i < lines.length; i++) {
    const match = lines[i].match(assignRegex)
    if (!match) continue

    const fullVar = match[1]
    const rawValue = match[2].trim()

    if (SKIP_VARIABLES.has(fullVar)) continue
    if (existingVarNames.has(fullVar)) continue
    if (seenVars.has(fullVar)) continue

    if (!/^['"].*['"]$/.test(rawValue) &&
        !/^-?\d+(\.\d+)?$/.test(rawValue) &&
        !/^\$(true|false)$/i.test(rawValue)) {
      continue
    }

    if (/\{\{.*?\}\}/.test(rawValue)) continue

    seenVars.add(fullVar)

    const varName = fullVar.replace(/^\$/, '')
    const key = toSnakeCase(varName)
    const { type, sensitive, reason } = inferType(null, rawValue, varName)
    const defaultValue = parseDefault(rawValue, type)

    candidates.push({
      variableName: fullVar,
      suggestedKey: key,
      suggestedLabel: toLabel(key),
      suggestedType: type,
      sensitive,
      defaultValue,
      source: 'assignment',
      lineNumber: i + 1,
      matchedLine: lines[i].trim(),
      inferenceReason: reason,
    })
  }

  return candidates
}

/**
 * Detect parameter candidates in a PowerShell script body.
 */
export function detectParameterCandidates(script: string): ParameterCandidate[] {
  if (!script.trim()) return []

  const paramBlock = findScriptLevelParamBlock(script)
  const paramCandidates = paramBlock
    ? extractParamBlockCandidates(script, paramBlock)
    : []

  const paramVarNames = new Set(paramCandidates.map(c => c.variableName))
  const assignmentCandidates = extractAssignmentCandidates(script, paramVarNames)

  return [...paramCandidates, ...assignmentCandidates]
}
