// Parses and locates {{...}} content markers within schedule bodies.

// Parses a single {{...}} block into a structured marker object (break, schedule, or content).
export function parseMarker(block) {
  const inner = block.slice(2, -2)
  const firstLine = inner.split('\n')[0].trim()
  const tokens = firstLine.split(/[\s,]+/).filter(Boolean)
  let i = 0

  const first = tokens[i++]

  if (first === 'break') {
    const type = (tokens[i] && !tokens[i].includes(':') && !/^\d+m(?:in)?$/.test(tokens[i]))
      ? tokens[i++] : 'end'
    if (type === 'end') return { kind: 'deprecated-end' }
    const kv = parseKvTokens(tokens.slice(i))
    return { kind: 'break', type, ...(kv.duration != null ? { duration: kv.duration } : {}) }
  }

  if (first === 'schedule') {
    return { kind: 'schedule', slug: tokens[i] }
  }

  const slug = first === 'topic' ? tokens[i++] : first
  const kv = parseKvTokens(tokens.slice(i))
  const { duration, ...rest } = kv
  return { kind: 'content', slug, ...(duration != null ? { duration } : {}), kv: rest }
}

// Parses key-value token pairs from a marker's trailing tokens, including bare duration strings (e.g. "90m").
function parseKvTokens(tokens) {
  const result = {}
  let i = 0
  while (i < tokens.length) {
    const token = tokens[i++]
    if (/^\d+m(?:in)?$/.test(token)) { // bare duration: "90m" or "90min"
      result.duration = Number(token.replace(/m(?:in)?$/, ''))
      continue
    }
    if (token.endsWith(':')) {
      const key = token.slice(0, -1)
      const raw = tokens[i++] ?? ''
      if (key === 'duration' && /^\d+m(?:in)?$/.test(raw)) {
        result.duration = Number(raw.replace(/m(?:in)?$/, ''))
      } else {
        result[key] = parseScalar(raw)
      }
      continue
    }
    const colonIdx = token.indexOf(':')
    if (colonIdx > 0) {
      const key = token.slice(0, colonIdx)
      const raw = token.slice(colonIdx + 1)
      if (key === 'duration' && /^\d+m(?:in)?$/.test(raw)) {
        result.duration = Number(raw.replace(/m(?:in)?$/, ''))
      } else {
        result[key] = parseScalar(raw)
      }
      continue
    }
    throw new Error(`invalid marker kv: ${token}`)
  }
  return result
}

// Coerces a raw string token to boolean, number, single-quoted string, or plain string.
function parseScalar(raw) {
  if (raw === 'true') return true
  if (raw === 'false') return false
  if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw)
  if (raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1).replace(/''/g, "'")
  return raw
}

// Replaces fenced code block content with spaces, preserving string length so regex positions stay aligned.
function maskFences(body) {
  const lines = body.split('\n')
  let inFence = false
  let fenceLen = 0
  const result = []
  for (const line of lines) {
    if (!inFence) {
      const m = line.match(/^(`{3,})/)
      if (m) { inFence = true; fenceLen = m[1].length }
      result.push(line)
    } else {
      const m = line.match(/^(`{3,})\s*$/)
      if (m && m[1].length >= fenceLen) { inFence = false; fenceLen = 0; result.push(line) }
      else result.push(' '.repeat(line.length))
    }
  }
  return result.join('\n')
}

// Yields all {{...}} markers and *** day separators, skipping those inside fenced code blocks.
export function* findMarkers(body) {
  const masked = maskFences(body)
  const reMarker = /^\{\{([\s\S]*?)\}\}/gm
  const reDay = /^\*\*\*$/gm

  const hits = []
  for (const m of masked.matchAll(reMarker)) hits.push({ m, day: false })
  for (const m of masked.matchAll(reDay)) hits.push({ m, day: true })
  hits.sort((a, b) => a.m.index - b.m.index)

  for (const { m, day } of hits) {
    if (day) {
      yield { parsed: { kind: 'break', type: 'end' }, start: m.index, end: m.index + 3 }
    } else {
      const block = body.slice(m.index, m.index + m[0].length)
      yield { parsed: parseMarker(block), start: m.index, end: m.index + m[0].length }
    }
  }
}
