import { normalizeNewlines, escapeRegExp, unquoteValue } from './utils.js'

export function isExtraTextBlock(name) {
  return name === 'Доп. текст'
}

export function isListBlock(name) {
  return name?.toLowerCase().includes('список')
}

export function isProductBlock(content) {
  return normalizeNewlines(content).split('\n').some((line) => line.trim().startsWith('+products'))
}

export function trimTrailingEmptyLines(lines) {
  let end = lines.length
  while (end > 0 && lines[end - 1].trim() === '') end -= 1
  return lines.slice(0, end)
}

export function parseBlocks(text) {
  const lines = normalizeNewlines(text).split('\n')
  const blocks = []
  let current = null

  const pushCurrent = () => {
    if (!current) return
    const trimmed = trimTrailingEmptyLines(current.lines)
    blocks.push({
      name: current.name,
      content: trimmed.join('\n').trimEnd(),
    })
  }

  for (const line of lines) {
    const isTopLevelComment = line.startsWith('//')
    if (isTopLevelComment) {
      pushCurrent()
      current = { name: line.replace(/^\/\/+\s?/, '').trim(), lines: [] }
      continue
    }
    if (!current) continue
    current.lines.push(line)
  }

  pushCurrent()
  return blocks.filter((block) => block.name && !isExtraTextBlock(block.name))
}

export function getMixinName(line) {
  const match = line.trim().match(/^\+([a-zA-Z0-9_]+)/)
  return match ? match[1] : ''
}

export function getDefaultMixinFieldLabel(type) {
  if (type === 'mixin-href') return 'Ссылка'
  if (type === 'mixin-ids') return 'ID товаров'
  if (type === 'mixin-opts') return 'Опции'
  return 'Текст'
}

export function normalizeMixinRules(rules) {
  if (!Array.isArray(rules)) return []
  return rules
    .map((rule) => ({
      mixin: String(rule?.mixin || '').trim(),
      argIndex: Math.max(0, Number(rule?.argIndex) || 0),
      type: String(rule?.type || 'mixin-text'),
      label: String(rule?.label || '').trim(),
    }))
    .filter((rule) => rule.mixin)
}

export function getMixinRulesForLine(line, mixinRules) {
  const mixinName = getMixinName(line)
  if (!mixinName) return []
  return normalizeMixinRules(mixinRules || []).filter((rule) => rule.mixin === mixinName)
}

export function isLikelyUiTextValue(value) {
  const v = String(value ?? '').trim()
  if (!v) return false
  if (/^(https?:\/\/|\/)/i.test(v)) return false
  if (/^\d+(?:[.,]\d+)?%?$/.test(v)) return false
  if (/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(v)) return false
  if (/^(left|right|center|top|bottom|middle|cover|contain|auto|true|false|none)$/i.test(v)) return false
  return /[A-Za-zА-Яа-яЁё]/.test(v)
}

export function guessAutoMixinField(rawArg, argIndex) {
  const raw = String(rawArg || '').trim()
  if (!raw) return { type: 'mixin-text', label: 'Текст' }
  const first = raw[0]
  const last = raw[raw.length - 1]
  const unquoted =
    (first === '"' && last === '"') || (first === "'" && last === "'")
      ? raw.slice(1, -1)
      : raw

  if (/^\d+(?:\s*,\s*\d+)+$/.test(unquoted)) {
    return { type: 'mixin-ids', label: 'ID товаров' }
  }
  if (raw.startsWith('{') && raw.endsWith('}')) {
    return { type: 'mixin-opts', label: 'Опции' }
  }
  if (/^(https?:\/\/|\/)/i.test(unquoted)) {
    if (/\.(png|jpe?g|gif|webp|svg)(\?|#|$)/i.test(unquoted)) {
      return { type: 'mixin-href', label: 'Картинка' }
    }
    return { type: 'mixin-href', label: argIndex === 0 ? 'Ссылка' : `Ссылка ${argIndex + 1}` }
  }
  return { type: 'mixin-text', label: 'Текст' }
}

export function guessObjectFieldByKey(key, value) {
  const name = String(key || '').toLowerCase()
  const val = String(value || '')
  if (name.includes('image') || name.includes('img') || name.includes('src') || name.includes('banner')) {
    return { type: 'mixin-href', label: 'Картинка' }
  }
  if (name.includes('href') || name.includes('url') || name.includes('link')) {
    return { type: 'mixin-href', label: 'Ссылка' }
  }
  if (/^\d+(?:\s*,\s*\d+)+$/.test(val)) {
    return { type: 'mixin-ids', label: 'ID товаров' }
  }
  return { type: 'mixin-text', label: key }
}

export function shouldExposeObjectField(key, value, guessedType) {
  const k = String(key ?? '').toLowerCase()
  const v = String(value ?? '').trim()
  if (!k) return false
  if (guessedType === 'mixin-href' || guessedType === 'mixin-ids') return true
  if (/text|title|header|caption|description|desc|label|button|btn|cta|name/.test(k)) return true
  if (/^(src|img|image|icon|logo|photo|pic|picture|poster|thumb|preview|banner|cover)$/.test(k)) return true
  if (/width|height|size|align|valign|padding|margin|radius|border|line|space|offset|top|left|right|bottom|color|bg|background|show|hide|count|qty|sort|order|mode|type|target/.test(k)) {
    return false
  }
  return isLikelyUiTextValue(v)
}

export function shouldExposeAutoMixinArg(line, rawArg, guessedType, argIndex) {
  if (guessedType === 'mixin-href' || guessedType === 'mixin-ids') return true
  if (guessedType === 'mixin-opts') return false
  const raw = String(rawArg ?? '').trim()
  const unquoted = unquoteValue(raw)
  // Allow empty quoted strings — user may have cleared the field
  if (!unquoted && (raw === '""' || raw === "''")) {
    const mixin = getMixinName(line).toLowerCase()
    if (/button|title|text|caption|banner|image|img|link|href|url|header|product/.test(mixin)) return true
  }
  if (!isLikelyUiTextValue(unquoted)) return false
  const mixin = getMixinName(line).toLowerCase()
  if (/button|title|text|caption|banner|image|img|link|href|url|header|product/.test(mixin)) return true
  return argIndex === 0 && unquoted.length > 5
}

export function parseMixinObjectArg(rawArg) {
  const raw = String(rawArg || '').trim()
  if (!(raw.startsWith('{') && raw.endsWith('}'))) return []
  const body = raw.slice(1, -1)
  const result = []
  const regex = /([a-zA-Z0-9_]+)\s*:\s*("(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^,}]+)/g
  let match
  while ((match = regex.exec(body))) {
    const key = match[1]
    const rawValue = (match[2] || '').trim()
    const value = unquoteValue(rawValue)
    result.push({ key, rawValue, value })
  }
  return result
}

export function parseQuotedArgs(line) {
  const args = []
  const regex = /"([^"]*)"|'([^']*)'/g
  let match
  while ((match = regex.exec(line))) {
    const value = match[1] ?? match[2] ?? ''
    const valueIndex = 1
    const start = match.index + valueIndex
    const end = start + value.length
    args.push({ value, start, end })
  }
  return args
}

export function findMixinArgRange(line, argIndex) {
  const openIdx = line.indexOf('(')
  if (openIdx === -1) return null
  let i = openIdx + 1
  let depthBrace = 0
  let depthBracket = 0
  let depthParen = 0
  let inQuote = ''
  const ranges = []
  let argStart = i
  for (; i < line.length; i += 1) {
    const ch = line[i]
    if (inQuote) {
      if (ch === inQuote && line[i - 1] !== '\\') inQuote = ''
      continue
    }
    if (ch === '"' || ch === "'") {
      inQuote = ch
      continue
    }
    if (ch === '{') depthBrace += 1
    if (ch === '}') depthBrace = Math.max(0, depthBrace - 1)
    if (ch === '[') depthBracket += 1
    if (ch === ']') depthBracket = Math.max(0, depthBracket - 1)
    if (ch === '(') depthParen += 1
    if (ch === ')') depthParen = Math.max(0, depthParen - 1)
    if (ch === ')' && depthBrace === 0 && depthBracket === 0 && depthParen === 0) {
      ranges.push([argStart, i])
      break
    }
    if (ch === ',' && depthBrace === 0 && depthBracket === 0 && depthParen === 0) {
      ranges.push([argStart, i])
      argStart = i + 1
    }
  }
  if (!ranges[argIndex]) return null
  let [start, end] = ranges[argIndex]
  while (start < end && /\s/.test(line[start])) start += 1
  while (end > start && /\s/.test(line[end - 1])) end -= 1
  return { start, end }
}

export function getMixinArgCount(line) {
  let count = 0
  while (count < 12 && findMixinArgRange(line, count)) count += 1
  return count
}

export function extractMixinArgValue(line, argIndex) {
  const range = findMixinArgRange(line, argIndex)
  if (!range) return ''
  const raw = line.slice(range.start, range.end).trim()
  if (!raw) return ''
  const first = raw[0]
  const last = raw[raw.length - 1]
  if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
    return raw.slice(1, -1)
  }
  return raw
}

export function replaceMixinArgQuoted(line, argIndex, newValue) {
  const range = findMixinArgRange(line, argIndex)
  if (!range) return line
  return line.slice(0, range.start) + `"${newValue}"` + line.slice(range.end)
}

export function replaceMixinArgRaw(line, argIndex, newValue) {
  const range = findMixinArgRange(line, argIndex)
  if (!range) return line
  return line.slice(0, range.start) + newValue + line.slice(range.end)
}

export function replaceObjectPropInArg(rawArg, key, newValue) {
  const keyPattern = new RegExp(
    `(${escapeRegExp(key)}\\s*:\\s*)("(?:\\\\.|[^"])*"|'(?:\\\\.|[^'])*'|[^,}]+)`
  )
  const match = rawArg.match(keyPattern)
  if (!match) return rawArg
  const oldToken = String(match[2] || '').trim()
  const quote = oldToken.startsWith("'") && oldToken.endsWith("'") ? "'" : '"'
  const escaped = String(newValue ?? '')
    .replace(/\\/g, '\\\\')
    .replace(new RegExp(escapeRegExp(quote), 'g'), `\\${quote}`)
  return rawArg.replace(keyPattern, `$1${quote}${escaped}${quote}`)
}

export function splitTextField(line) {
  const leading = line.match(/^\s*/)?.[0].length ?? 0
  let depth = 0
  for (let i = leading; i < line.length; i += 1) {
    const ch = line[i]
    if (ch === '(') depth += 1
    if (ch === ')') depth = Math.max(0, depth - 1)
    if (ch === ' ' && depth === 0) {
      const text = line.slice(i + 1)
      return {
        start: i + 1,
        end: i + 1 + text.length,
        value: text,
      }
    }
  }
  return null
}

export function getParagraphMeta(line) {
  if (!line.trim()) return null
  const trimmed = line.trim()
  if (trimmed.startsWith('//') || trimmed.startsWith('+') || trimmed.startsWith('-')) return null
  const meta = splitTextField(line)
  if (!meta) return null
  const tagMatch = trimmed.match(/^([a-zA-Z0-9-]+)/)
  const tag = tagMatch ? tagMatch[1].toLowerCase() : ''
  const structuralTags = ['tr', 'td', 'table', 'tbody', 'thead', 'tfoot']
  if (structuralTags.includes(tag)) return null
  return meta
}

export function getListItemRanges(lines) {
  const ranges = []
  for (let i = 0; i < lines.length; i += 1) {
    const line = lines[i]
    if (line.trim() !== 'tr') continue
    const indent = line.match(/^\s*/)?.[0].length ?? 0
    if (indent === 0) continue
    let nextNonEmpty = ''
    for (let j = i + 1; j < Math.min(lines.length, i + 6); j += 1) {
      if (lines[j].trim()) {
        nextNonEmpty = lines[j].trim()
        break
      }
    }
    if (!nextNonEmpty.startsWith('+tdFixed')) continue
    const slice = lines.slice(i, i + 8).join('\n')
    if (!slice.includes('span.text')) continue
    ranges.push({ start: i })
  }
  if (!ranges.length) return []
  for (let i = 0; i < ranges.length; i += 1) {
    const nextStart = ranges[i + 1]?.start ?? lines.length
    ranges[i].end = nextStart - 1
  }
  return ranges
}

export function getListItemTextLine(lines, range) {
  for (let i = range.start; i <= range.end; i += 1) {
    const line = lines[i]
    if (!line) continue
    if (!line.includes('span.text')) continue
    return i
  }
  return -1
}

export function buildBaseSchema(content, blockName, mixinRules = []) {
  if (blockName && blockName.toLowerCase().includes('3 товара в ряд')) {
    return [
      {
        type: 'raw',
        label: 'Код блока',
      },
    ]
  }
  const lines = normalizeNewlines(content).split('\n')
  const fields = []
  let paragraphIndex = 0

  if (isListBlock(blockName)) {
    const ranges = getListItemRanges(lines)
    if (ranges.length === 0) {
      // Name says "список" but no list-item structure found — fall through to generic parsing
    } else {
    ranges.forEach((range, index) => {
      const lineIndex = getListItemTextLine(lines, range)
      if (lineIndex === -1) return
      fields.push({
        type: 'list-item',
        label: 'Пункт',
        lineIndex,
        itemIndex: index,
        removable: index > 0,
      })
    })
    return fields
    }
  }

  lines.forEach((line, lineIndex) => {
    if (!line.trim()) return
    const trimmed = line.trim()
    const customMixinRules = getMixinRulesForLine(trimmed, mixinRules)
    if (customMixinRules.length) {
      customMixinRules.forEach((rule) => {
        const argIndex = Math.max(0, Number(rule.argIndex) || 0)
        if (!findMixinArgRange(line, argIndex)) return
        fields.push({
          type: rule.type,
          label: rule.label || getDefaultMixinFieldLabel(rule.type),
          lineIndex,
          argIndex,
        })
      })
      return
    }
    if (trimmed.startsWith('+buttonRounded')) {
      const args = parseQuotedArgs(line)
      if (args[0]) {
        fields.push({
          type: 'mixin-text',
          label: 'Текст кнопки',
          lineIndex,
          argIndex: 0,
        })
      }
      if (args[1]) {
        fields.push({
          type: 'mixin-href',
          label: 'Ссылка кнопки',
          lineIndex,
          argIndex: 1,
        })
      }
      return
    }
    if (trimmed.startsWith('+products') || trimmed.startsWith('+product')) {
      if (findMixinArgRange(line, 0)) {
        fields.push({
          type: 'mixin-ids',
          label: 'ID товаров',
          lineIndex,
          argIndex: 0,
        })
      }
      return
    }
    if (trimmed.startsWith('+')) {
      const argCount = getMixinArgCount(line)
      for (let argIndex = 0; argIndex < argCount; argIndex += 1) {
        const range = findMixinArgRange(line, argIndex)
        if (!range) continue
        const rawArg = line.slice(range.start, range.end)
        const objectFields = parseMixinObjectArg(rawArg)
        if (objectFields.length) {
          objectFields.forEach((item) => {
            const guessedByKey = guessObjectFieldByKey(item.key, item.value)
            if (!shouldExposeObjectField(item.key, item.value, guessedByKey.type)) return
            fields.push({
              type: guessedByKey.type,
              label: guessedByKey.label,
              lineIndex,
              argIndex,
              objectKey: item.key,
            })
          })
          continue
        }
        let guessed = guessAutoMixinField(rawArg, argIndex)
        // For image/background mixins, first quoted arg is likely an image URL
        if (argIndex === 0 && guessed.type === 'mixin-text') {
          const mn = getMixinName(line).toLowerCase()
          if (/image|img|background|banner|poster|cover|photo/.test(mn)) {
            guessed = { type: 'mixin-href', label: 'Картинка' }
          }
        }
        if (!shouldExposeAutoMixinArg(line, rawArg, guessed.type, argIndex)) continue
        fields.push({
          type: guessed.type,
          label: guessed.label,
          lineIndex,
          argIndex,
        })
      }
      return
    }
    if (trimmed.startsWith('//') || trimmed.startsWith('+') || trimmed.startsWith('-')) return

    const textField = splitTextField(line)
    if (textField) {
      if (blockName === 'Текст') {
        const paragraphMeta = getParagraphMeta(line)
        if (!paragraphMeta) return
        paragraphIndex += 1
        fields.push({
          type: 'text',
          label: 'Текст',
          lineIndex,
          paragraphIndex,
          removable: paragraphIndex > 1,
        })
      } else if (textField.value.trim() !== '' || getParagraphMeta(line)) {
        fields.push({
          type: 'text',
          label: 'Текст',
          lineIndex,
        })
      }
    }

    const linkRegex = /(href|src)=("([^"]*)"|'([^']*)')/g
    let match
    let occurrence = 0
    while ((match = linkRegex.exec(line))) {
      fields.push({
        type: match[1],
        label: match[1] === 'href' ? 'Ссылка' : 'Картинка',
        lineIndex,
        occurrence,
      })
      occurrence += 1
    }
  })

  return fields
}

export function guessSectionLabel(lines) {
  for (const line of lines) {
    const t = line.trim()
    if (t.startsWith('//---')) return t.replace(/^\/\/---\s*/, '').trim() || 'Секция'
    if (/\+button/i.test(t)) return 'Кнопка'
    if (/\+backgroundImage/i.test(t) || /img\(/.test(t)) return 'Картинка'
    if (/\+product/i.test(t)) return 'Товары'
    if (/span\.\S*header/i.test(t)) return 'Заголовок'
    if (/span\.\S*text/i.test(t)) return 'Текст'
    if (/^a\(href/i.test(t)) return 'Ссылка'
  }
  return 'Секция'
}

function parseAtIndent(lines, targetIndent) {
  const prefix = []
  const columns = []
  const separators = []
  const suffix = []

  let state = 'prefix'
  let currentSections = []
  let currentGroup = null
  let gapLines = []

  const isTarget = (stripped, indent) =>
    indent === targetIndent && (/^tr\b/.test(stripped) || /^\+spacerLine\(/.test(stripped) || /^\/\/---/.test(stripped))

  const finishGroup = () => {
    if (currentGroup) { currentSections.push(currentGroup); currentGroup = null }
  }
  const finishColumn = () => {
    finishGroup()
    if (currentSections.length > 0) {
      columns.push({ sections: currentSections })
      currentSections = []
    }
  }

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]
    const t = line.trimEnd()
    const stripped = t.trimStart()
    const indent = t.length - stripped.length

    if (state === 'prefix') {
      if (isTarget(stripped, indent)) {
        state = 'sections'
      } else {
        prefix.push(line)
        continue
      }
    }

    if (state === 'gap') {
      if (isTarget(stripped, indent)) {
        separators.push(gapLines)
        gapLines = []
        currentSections = []
        state = 'sections'
      } else {
        gapLines.push(line)
        continue
      }
    }

    // state === 'sections'
    if (indent === targetIndent) {
      if (/^\+spacerLine\(/.test(stripped)) {
        finishGroup()
        currentSections.push({ type: 'spacer', lines: [line], startLine: i })
      } else if (/^tr\b/.test(stripped) || /^\/\/---/.test(stripped)) {
        finishGroup()
        currentGroup = { type: 'section', lines: [line], startLine: i }
      } else {
        if (currentGroup) currentGroup.lines.push(line)
      }
    } else if (indent > targetIndent && currentGroup) {
      currentGroup.lines.push(line)
    } else if (!stripped) {
      if (currentGroup) currentGroup.lines.push(line)
    } else {
      finishColumn()
      state = 'gap'
      gapLines = [line]
    }
  }

  if (state === 'sections') {
    finishColumn()
  } else if (state === 'gap') {
    suffix.push(...gapLines)
  }

  if (columns.length === 0) return null

  // Add labels
  for (const col of columns) {
    for (const s of col.sections) {
      if (s.type === 'section') s.label = guessSectionLabel(s.lines)
    }
  }

  // First column must have at least 2 content sections
  const firstContentSecs = columns[0].sections.filter(s => s.type === 'section')
  if (firstContentSecs.length < 2) return null

  // For multi-column: verify all columns have same number of content sections
  if (columns.length > 1) {
    for (let c = 1; c < columns.length; c++) {
      const n = columns[c].sections.filter(s => s.type === 'section').length
      if (n !== firstContentSecs.length) return null
    }
  }

  // Compute spacer values from first column
  const spacerItems = columns[0].sections.filter(s => s.type === 'spacer')
  let topSpacerValue = 40, bottomSpacerValue = 40
  const extractVal = (s) => { const m = s.lines[0].match(/\+spacerLine\((\d+)/); return m ? parseInt(m[1]) : 20 }
  if (spacerItems.length >= 1) topSpacerValue = extractVal(spacerItems[0])
  if (spacerItems.length >= 2) bottomSpacerValue = extractVal(spacerItems[spacerItems.length - 1])

  return { prefix, columns, separators, suffix, targetIndent, topSpacerValue, bottomSpacerValue }
}

export function parseSections(content) {
  if (!content) return null
  const lines = normalizeNewlines(content).split('\n')

  // Collect indent levels where tr and +spacerLine appear
  const indentCounts = new Map()
  for (const line of lines) {
    const t = line.trimEnd()
    if (!t) continue
    const indent = t.length - t.trimStart().length
    const stripped = t.trimStart()
    if (/^tr\b/.test(stripped) || /^\+spacerLine\(/.test(stripped)) {
      indentCounts.set(indent, (indentCounts.get(indent) || 0) + 1)
    }
  }
  if (indentCounts.size === 0) return null

  // Try indent levels from shallowest to deepest
  const sortedIndents = [...indentCounts.entries()]
    .filter(([, count]) => count >= 3)
    .sort(([a], [b]) => a - b)

  for (const [targetIndent] of sortedIndents) {
    const result = parseAtIndent(lines, targetIndent)
    if (result) return result
  }
  return null
}

function normalizeSpacers(sections, topVal, bottomVal) {
  const spacers = sections.filter(s => s.type === 'spacer')
  if (spacers.length < 2) return
  const setVal = (s, val) => { s.lines[0] = s.lines[0].replace(/\+spacerLine\(\d+/, `+spacerLine(${val}`) }
  for (const s of spacers) setVal(s, 20)
  setVal(spacers[0], topVal || 40)
  setVal(spacers[spacers.length - 1], bottomVal || 40)
}

export function rebuildContentFromSections(parsed) {
  for (const col of parsed.columns) {
    normalizeSpacers(col.sections, parsed.topSpacerValue, parsed.bottomSpacerValue)
  }
  const result = [...parsed.prefix]
  for (let c = 0; c < parsed.columns.length; c++) {
    result.push(...parsed.columns[c].sections.flatMap(s => s.lines))
    if (c < parsed.separators.length) {
      result.push(...parsed.separators[c])
    }
  }
  result.push(...parsed.suffix)
  return result.join('\n')
}
