#!/usr/bin/env bash
# lt.sh - Convert LightText to .html, .md, .rtf
# 100% bash. No python, no perl, no awk/sed dependency for the conversion logic.
set -euo pipefail

if [ $# -ne 2 ]; then
  echo "Usage: $0 input.txt output.[html|md|rtf]"
  exit 1
fi

INPUT="$1"
OUTPUT="$2"

[ -f "$INPUT" ] || { echo "Input not found: $INPUT"; exit 1; }

# ---------- string helpers ----------

ltrim() { local s="$1"; s="${s#"${s%%[![:space:]]*}"}"; printf '%s' "$s"; }
rtrim() { local s="$1"; s="${s%"${s##*[![:space:]]}"}"; printf '%s' "$s"; }
trim()  { rtrim "$(ltrim "$1")"; }

is_blank() { [ -z "$(trim "$1")" ]; }

is_all_caps() {
  local s; s="$(trim "$1")"
  [ -z "$s" ] && return 1
  [[ "$s" =~ [A-Za-z] ]] || return 1
  [ "$s" = "${s^^}" ]
}

title_case() {
  # "hello world" -> "Hello World" (used for markdown heading text)
  local s="$1" out="" word
  s="$(trim "$s")"
  s="${s,,}"
  for word in $s; do
    out+="${word^}"" "
  done
  rtrim "$out"
}

html_escape() {
  local s="$1"
  s="${s//&/&amp;}"
  s="${s//</&lt;}"
  s="${s//>/&gt;}"
  s="${s//\"/&quot;}"
  s="${s//\'/&#x27;}"
  printf '%s' "$s"
}

rtf_escape() {
  local s="$1"
  s="${s//\\/\\\\}"
  s="${s//\{/\\{}"
  s="${s//\}/\\\}}"
  printf '%s' "$s"
}

# Repeatedly extract [label > url] and replace with a placeholder, storing
# the parts in the global LINK_LABELS / LINK_URLS arrays. Call before escaping.
LINK_LABELS=()
LINK_URLS=()
EXTRACTED=""
# Sets the global EXTRACTED and LINK_LABELS/LINK_URLS arrays. Must be called
# directly (not inside a $(...) subshell) or the array writes will be lost
# when the subshell exits.
extract_links() {
  local s="$1" full label url prefix suffix idx
  LINK_LABELS=()
  LINK_URLS=()
  while [[ "$s" =~ \[([^]\>]+)\>([^]]+)\] ]]; do
    full="${BASH_REMATCH[0]}"
    label="$(trim "${BASH_REMATCH[1]}")"
    url="$(trim "${BASH_REMATCH[2]}")"
    idx=${#LINK_LABELS[@]}
    LINK_LABELS+=("$label")
    LINK_URLS+=("$url")
    prefix="${s%%"$full"*}"
    suffix="${s#*"$full"}"
    s="${prefix}"$'\x01'"LINK${idx}"$'\x01'"${suffix}"
  done
  EXTRACTED="$s"
}

# Apply *bold* -> open+inner+close, on already-escaped text.
# Two passes: first extract every raw match into a placeholder (so scanning
# never sees the original text again), then substitute the markup at the
# end. Doing it in one pass would break when open/close reuse the trigger
# character (markdown bold: *text* -> **text** would get rescanned forever).
apply_wrap() {
  local s="$1" ch="$2" open="$3" close="$4" full inner prefix suffix idx=0
  local re="\\${ch}([^${ch}]+)\\${ch}"
  local captured=()
  while [[ "$s" =~ $re ]]; do
    full="${BASH_REMATCH[0]}"
    inner="${BASH_REMATCH[1]}"
    captured+=("$inner")
    prefix="${s%%"$full"*}"
    suffix="${s#*"$full"}"
    s="${prefix}"$'\x02'"W${idx}"$'\x02'"${suffix}"
    idx=$((idx + 1))
  done
  local i ph
  for ((i = 0; i < ${#captured[@]}; i++)); do
    ph=$'\x02'"W${i}"$'\x02'
    s="${s//"$ph"/${open}${captured[$i]}${close}}"
  done
  printf '%s' "$s"
}

restore_links() {
  # $1 = string with placeholders, $2 = format (html|md|rtf)
  local s="$1" fmt="$2" i label url rendered ph
  for ((i = 0; i < ${#LINK_LABELS[@]}; i++)); do
    label="${LINK_LABELS[$i]}"
    url="${LINK_URLS[$i]}"
    ph=$'\x01'"LINK${i}"$'\x01'
    case "$fmt" in
      html) rendered="<a href=\"$(html_escape "$url")\">$(html_escape "$label")</a>" ;;
      md)   rendered="[${label}](${url})" ;;
      rtf)  rendered="$(rtf_escape "$label") ($(rtf_escape "$url"))" ;;
    esac
    s="${s//"$ph"/$rendered}"
  done
  printf '%s' "$s"
}

parse_inline_html() {
  local s; extract_links "$1"; s="$EXTRACTED"
  s="$(html_escape "$s")"
  s="$(apply_wrap "$s" '*' '<strong>' '</strong>')"
  s="$(apply_wrap "$s" '_' '<em>' '</em>')"
  restore_links "$s" html
}

parse_inline_md() {
  local s; extract_links "$1"; s="$EXTRACTED"
  s="$(apply_wrap "$s" '*' '**' '**')"
  s="$(apply_wrap "$s" '_' '*' '*')"
  restore_links "$s" md
}

parse_inline_rtf() {
  local s; extract_links "$1"; s="$EXTRACTED"
  s="$(rtf_escape "$s")"
  s="$(apply_wrap "$s" '*' '{\b ' '}')"
  s="$(apply_wrap "$s" '_' '{\i ' '}')"
  restore_links "$s" rtf
}

# ---------- read input into an array of lines ----------

LINES=()
while IFS= read -r line || [ -n "$line" ]; do
  LINES+=("$line")
done < "$INPUT"
N=${#LINES[@]}

is_indented() { [[ "${LINES[$1]}" == "    "* ]]; }
is_list_item() { [[ "$(ltrim "${LINES[$1]}")" == "- "* ]]; }
line_blank() { is_blank "${LINES[$1]:-}"; }
is_heading_line() {
  local i=$1 prev_blank next_blank
  [ "$i" -eq 0 ] && prev_blank=1 || { line_blank $((i - 1)) && prev_blank=1 || prev_blank=0; }
  [ "$i" -eq $((N - 1)) ] && next_blank=1 || { line_blank $((i + 1)) && next_blank=1 || next_blank=0; }
  [ "$prev_blank" -eq 1 ] && [ "$next_blank" -eq 1 ] && is_all_caps "${LINES[$i]}"
}

# ---------- HTML ----------

to_html_body() {
  local i=0 out="" line
  while [ "$i" -lt "$N" ]; do
    line="${LINES[$i]}"
    if is_indented "$i" && ! is_blank "$line"; then
      local code="" first=1
      while [ "$i" -lt "$N" ] && { is_indented "$i" || { is_blank "${LINES[$i]}" && [ $((i + 1)) -lt "$N" ] && is_indented $((i + 1)); }; }; do
        local content=""
        if is_indented "$i"; then content="${LINES[$i]:4}"; fi
        if [ "$first" -eq 1 ]; then code="$content"; first=0; else code+=$'\n'"$content"; fi
        i=$((i + 1))
      done
      out+="<pre><code>$(html_escape "$code")</code></pre>"$'\n'
      continue
    fi
    if is_blank "$line"; then i=$((i + 1)); continue; fi
    if is_list_item "$i"; then
      out+="<ul>"$'\n'
      while [ "$i" -lt "$N" ] && is_list_item "$i"; do
        local litem; litem="$(ltrim "${LINES[$i]}")"
        out+="<li>$(parse_inline_html "${litem:2}")</li>"$'\n'
        i=$((i + 1))
      done
      out+="</ul>"$'\n'
      continue
    fi
    if is_heading_line "$i"; then
      out+="<h2>$(parse_inline_html "$(trim "$line")")</h2>"$'\n'
      i=$((i + 1))
      continue
    fi
    local para=""
    while [ "$i" -lt "$N" ] && ! is_blank "${LINES[$i]}" && ! is_indented "$i" && ! is_list_item "$i" && ! is_heading_line "$i"; do
      if [ -z "$para" ]; then para="$(trim "${LINES[$i]}")"; else para+=" $(trim "${LINES[$i]}")"; fi
      i=$((i + 1))
    done
    out+="<p>$(parse_inline_html "$para")</p>"$'\n'
  done
  printf '%s' "$out"
}

to_html_full() {
  local body title
  body="$(to_html_body)"
  title="$(html_escape "$(basename "$INPUT")")"
  cat <<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${title}</title>
<style>
  body{max-width:720px;margin:3rem auto;padding:0 1.5rem;font-family:Georgia,serif;line-height:1.7;color:#000;background:#fff}
  h2{font-family:system-ui,sans-serif;letter-spacing:.04em;margin-top:3rem;font-size:1.25rem;color:#000}
  a{color:#0000ee;text-decoration:underline;text-underline-offset:3px}
  ul{padding-left:1.2rem} li{margin:.3rem 0}
  pre{background:#f5f5f5;padding:1rem;overflow:auto;border-radius:8px;font-size:.9rem} code{font-family:ui-monospace,Menlo,monospace}
  p{margin:1rem 0}
</style>
</head>
<body>
${body}
</body>
</html>
HTML
}

# ---------- Markdown ----------

to_md() {
  local i=0 out="" line
  while [ "$i" -lt "$N" ]; do
    line="${LINES[$i]}"
    if is_indented "$i" && ! is_blank "$line"; then
      out+='```'$'\n'
      while [ "$i" -lt "$N" ] && { is_indented "$i" || { is_blank "${LINES[$i]}" && [ $((i + 1)) -lt "$N" ] && is_indented $((i + 1)); }; }; do
        if is_indented "$i"; then out+="${LINES[$i]:4}"$'\n'; else out+=$'\n'; fi
        i=$((i + 1))
      done
      out+='```'$'\n\n'
      continue
    fi
    if is_blank "$line"; then out+=$'\n'; i=$((i + 1)); continue; fi
    if is_list_item "$i"; then
      while [ "$i" -lt "$N" ] && is_list_item "$i"; do
        local litem; litem="$(ltrim "${LINES[$i]}")"
        out+="- $(parse_inline_md "${litem:2}")"$'\n'
        i=$((i + 1))
      done
      out+=$'\n'
      continue
    fi
    if is_heading_line "$i"; then
      out+="# $(title_case "$line")"$'\n\n'
      i=$((i + 1))
      continue
    fi
    local para=""
    while [ "$i" -lt "$N" ] && ! is_blank "${LINES[$i]}" && ! is_indented "$i" && ! is_list_item "$i" && ! is_heading_line "$i"; do
      if [ -z "$para" ]; then para="$(trim "${LINES[$i]}")"; else para+=" $(trim "${LINES[$i]}")"; fi
      i=$((i + 1))
    done
    out+="$(parse_inline_md "$para")"$'\n\n'
  done
  out="$(trim "$out")"
  printf '%s\n' "$out"
}

# ---------- RTF ----------

to_rtf() {
  local i=0 body="" line
  while [ "$i" -lt "$N" ]; do
    line="${LINES[$i]}"
    if is_indented "$i" && ! is_blank "$line"; then
      body+='\pard\cbpat1 \f1\fs20 '
      while [ "$i" -lt "$N" ] && { is_indented "$i" || { is_blank "${LINES[$i]}" && [ $((i + 1)) -lt "$N" ] && is_indented $((i + 1)); }; }; do
        local content=""
        if is_indented "$i"; then content="${LINES[$i]:4}"; fi
        body+="$(rtf_escape "$content")"'\line '
        i=$((i + 1))
      done
      body+='\f0\fs24\par'$'\n'
      continue
    fi
    if is_blank "$line"; then body+='\par'$'\n'; i=$((i + 1)); continue; fi
    if is_list_item "$i"; then
      while [ "$i" -lt "$N" ] && is_list_item "$i"; do
        local litem item; litem="$(ltrim "${LINES[$i]}")"
        item="$(parse_inline_rtf "${litem:2}")"
        body+='\pard{\pntext\f0 \bullet\tab}{\*\pn\pnlvlblt\pnf0\pnindent0{\pntxtb\bullet}} '"${item}"'\par'$'\n'
        i=$((i + 1))
      done
      continue
    fi
    if is_heading_line "$i"; then
      body+='\pard\sb480\sa240\fs32\b '"$(rtf_escape "$(trim "$line")")"'\b0\fs24\par'$'\n'
      i=$((i + 1))
      continue
    fi
    local para=""
    while [ "$i" -lt "$N" ] && ! is_blank "${LINES[$i]}" && ! is_indented "$i" && ! is_list_item "$i" && ! is_heading_line "$i"; do
      if [ -z "$para" ]; then para="$(trim "${LINES[$i]}")"; else para+=" $(trim "${LINES[$i]}")"; fi
      i=$((i + 1))
    done
    body+='\pard '"$(parse_inline_rtf "$para")"'\par'$'\n'
  done
  printf '{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl{\\f0\\froman\\fcharset0 Times New Roman;}{\\f1\\fmodern\\fcharset0 Courier;}}\\f0\\fs24\n%s}\n' "$body"
}

# ---------- dispatch ----------

EXT="${OUTPUT##*.}"
EXT="${EXT,,}"

case "$EXT" in
  html) to_html_full > "$OUTPUT" ;;
  md)   to_md > "$OUTPUT" ;;
  rtf)  to_rtf > "$OUTPUT" ;;
  *)    echo "Unknown ext .$EXT"; exit 1 ;;
esac

echo "Wrote $OUTPUT"
