///| Link, image, wikilink and footnote reference parsing

///| (split from inline_parser.mbt).

///|
/// Split wiki destination into page target and optional fragment.
fn split_wikilink_destination(destination : String) -> (String, String) {
  let parts = destination.split("#").collect()
  if parts.length() <= 1 {
    (destination, "")
  } else {
    (parts[0].to_owned(), parts[1].to_owned())
  }
}

///|
/// Try to parse a wiki link: [[target]] or [[target|label]].
fn InlineParser::try_parse_wikilink(
  self : InlineParser,
  start : Int,
) -> Inline? {
  if !self.wikilinks || !self.scanner.matches("[[") {
    return None
  }
  self.scanner.advance(2) // Skip [[
  let target_buf = StringBuilder::new()
  let label_buf = StringBuilder::new()
  let mut in_label = false
  while !self.scanner.is_eof() {
    match self.scanner.peek() {
      Some(']') if char_is(self.scanner.peek_at(1), ']') => {
        self.scanner.advance(2)
        let raw_target = target_buf.to_string().trim(chars=" \t").to_owned()
        if raw_target.is_empty() {
          self.scanner.restore(start)
          return None
        }
        let raw_label = if in_label {
          label_buf.to_string().trim(chars=" \t").to_owned()
        } else {
          ""
        }
        let (target, fragment) = split_wikilink_destination(raw_target)
        return Some(
          Inline::WikiLink(
            target~,
            label=raw_label,
            fragment~,
            span=Span::new(start, self.scanner.pos),
          ),
        )
      }
      Some('\n') => {
        self.scanner.restore(start)
        return None
      }
      Some('[') if char_is(self.scanner.peek_at(1), '[') => {
        self.scanner.restore(start)
        return None
      }
      Some('|') if !in_label => {
        in_label = true
        self.scanner.advance(1)
      }
      Some('\\') => {
        self.scanner.advance(1)
        match self.scanner.consume() {
          Some(c) =>
            if in_label {
              label_buf.write_char(c)
            } else {
              target_buf.write_char(c)
            }
          None => break
        }
      }
      Some(c) => {
        if in_label {
          label_buf.write_char(c)
        } else {
          target_buf.write_char(c)
        }
        self.scanner.advance(1)
      }
      None => break
    }
  }
  self.scanner.restore(start)
  None
}

///|
/// Try to parse link
fn InlineParser::try_parse_link(self : InlineParser, start : Int) -> Inline? {
  self.scanner.advance(1) // Skip [

  // Parse link text
  let text_buf = StringBuilder::new()
  let mut bracket_depth = 1
  while !self.scanner.is_eof() && bracket_depth > 0 {
    match self.scanner.peek() {
      Some('[') => {
        bracket_depth += 1
        text_buf.write_char('[')
        self.scanner.advance(1)
      }
      Some(']') => {
        bracket_depth -= 1
        if bracket_depth > 0 {
          text_buf.write_char(']')
        }
        self.scanner.advance(1)
      }
      Some('\\') => {
        self.scanner.advance(1)
        match self.scanner.consume() {
          Some(c) => text_buf.write_char(c)
          None => break
        }
      }
      Some(c) => {
        text_buf.write_char(c)
        self.scanner.advance(1)
      }
      None => break
    }
  }
  let link_text = text_buf.to_string()

  // Check for inline link (url)
  if char_is(self.scanner.peek(), '(') {
    self.scanner.advance(1)

    // Parse URL
    let url_buf = StringBuilder::new()
    let mut paren_depth = 1

    // Skip leading whitespace
    let _ = self.scanner.skip_spaces()

    // Check for angle-bracketed URL
    if char_is(self.scanner.peek(), '<') {
      self.scanner.advance(1)
      let mut angle_url_has_newline = false
      while !self.scanner.is_eof() {
        match self.scanner.peek() {
          Some('>') => {
            self.scanner.advance(1)
            break
          }
          Some('\n') => {
            // Newline in angle-bracket URL invalidates the link
            angle_url_has_newline = true
            break
          }
          Some(c) => {
            url_buf.write_char(c)
            self.scanner.advance(1)
          }
          None => break
        }
      }
      if angle_url_has_newline {
        self.scanner.restore(start)
        return None
      }
    } else {
      let mut url_has_newline = false
      while !self.scanner.is_eof() {
        match self.scanner.peek() {
          Some('(') => {
            paren_depth += 1
            url_buf.write_char('(')
            self.scanner.advance(1)
          }
          Some(')') => {
            paren_depth -= 1
            if paren_depth == 0 {
              break
            }
            url_buf.write_char(')')
            self.scanner.advance(1)
          }
          Some(' ') | Some('\t') => break // End of URL
          Some('\n') => {
            // Newline in non-angle-bracket URL invalidates the link
            url_has_newline = true
            break
          }
          Some(c) => {
            url_buf.write_char(c)
            self.scanner.advance(1)
          }
          None => break
        }
      }
      // If URL contains newline, abort link parsing
      if url_has_newline {
        self.scanner.restore(start)
        return None
      }
    }
    let url = url_buf.to_string()

    // Skip whitespace (including newlines) before title
    while !self.scanner.is_eof() {
      match self.scanner.peek() {
        Some(' ') | Some('\t') | Some('\n') => self.scanner.advance(1)
        _ => break
      }
    }

    // Parse optional title (supports ", ', and () delimiters)
    let title = match self.scanner.peek() {
      Some('"') | Some('\'') => {
        let quote = match self.scanner.consume() {
          Some(c) => c
          None => '"'
        }
        let title_buf = StringBuilder::new()
        while !self.scanner.is_eof() {
          match self.scanner.peek() {
            Some(c) if c == quote => {
              self.scanner.advance(1)
              break
            }
            Some('\\') => {
              self.scanner.advance(1)
              match self.scanner.consume() {
                Some(c) => title_buf.write_char(c)
                None => break
              }
            }
            Some(c) => {
              title_buf.write_char(c)
              self.scanner.advance(1)
            }
            None => break
          }
        }
        title_buf.to_string()
      }
      Some('(') => {
        self.scanner.advance(1) // Skip opening (
        let title_buf = StringBuilder::new()
        let mut paren_depth = 1
        while !self.scanner.is_eof() && paren_depth > 0 {
          match self.scanner.peek() {
            Some(')') => {
              paren_depth -= 1
              if paren_depth == 0 {
                self.scanner.advance(1)
                break
              }
              title_buf.write_char(')')
              self.scanner.advance(1)
            }
            Some('(') => {
              paren_depth += 1
              title_buf.write_char('(')
              self.scanner.advance(1)
            }
            Some('\\') => {
              self.scanner.advance(1)
              match self.scanner.consume() {
                Some(c) => title_buf.write_char(c)
                None => break
              }
            }
            Some(c) => {
              title_buf.write_char(c)
              self.scanner.advance(1)
            }
            None => break
          }
        }
        title_buf.to_string()
      }
      _ => ""
    }

    // Skip whitespace and closing paren
    let _ = self.scanner.skip_spaces()
    if char_is(self.scanner.peek(), ')') {
      self.scanner.advance(1)

      // Parse link text as inlines
      let children = if self.scanner.is_bmp() {
        parse_inlines_known_bmp(link_text, wikilinks=self.wikilinks)
      } else {
        parse_inlines(link_text, wikilinks=self.wikilinks)
      }

      // CommonMark: Links cannot contain other links
      // If link text contains a link, the outer link is invalid
      if contains_link(children) {
        self.scanner.restore(start)
        return None
      }
      return Some(
        Inline::Link(
          children~,
          url~,
          title~,
          span=Span::new(start, self.scanner.pos),
        ),
      )
    }
  }

  // Check for reference link [text][ref]
  if char_is(self.scanner.peek(), '[') {
    self.scanner.advance(1)
    let label_buf = StringBuilder::new()
    while !self.scanner.is_eof() {
      match self.scanner.peek() {
        Some(']') => {
          self.scanner.advance(1)
          break
        }
        Some(c) => {
          label_buf.write_char(c)
          self.scanner.advance(1)
        }
        None => break
      }
    }
    let label = label_buf.to_string()
    let children = if self.scanner.is_bmp() {
      parse_inlines_known_bmp(link_text, wikilinks=self.wikilinks)
    } else {
      parse_inlines(link_text, wikilinks=self.wikilinks)
    }

    // CommonMark: Links cannot contain other links
    if contains_link(children) {
      self.scanner.restore(start)
      return None
    }
    return Some(
      Inline::RefLink(
        children~,
        label~,
        span=Span::new(start, self.scanner.pos),
      ),
    )
  }

  // Not a valid link
  self.scanner.restore(start)
  None
}

///|
/// Try to parse footnote reference [^label]
fn InlineParser::try_parse_footnote_reference(
  self : InlineParser,
  start : Int,
) -> Inline? {
  self.scanner.advance(1) // Skip [
  if !char_is(self.scanner.peek(), '^') {
    self.scanner.restore(start)
    return None
  }
  self.scanner.advance(1) // Skip ^

  // Read label (alphanumeric, -, _)
  let label_buf = StringBuilder::new()
  while !self.scanner.is_eof() {
    match self.scanner.peek() {
      Some(']') => break
      Some(c) if (c >= 'a' && c <= 'z') ||
        (c >= 'A' && c <= 'Z') ||
        (c >= '0' && c <= '9') ||
        c == '-' ||
        c == '_' => {
        label_buf.write_char(c)
        self.scanner.advance(1)
      }
      _ => {
        self.scanner.restore(start)
        return None
      }
    }
  }
  let label = label_buf.to_string()
  if label.is_empty() {
    self.scanner.restore(start)
    return None
  }
  if !char_is(self.scanner.peek(), ']') {
    self.scanner.restore(start)
    return None
  }
  self.scanner.advance(1) // Skip ]
  Some(
    Inline::FootnoteReference(label~, span=Span::new(start, self.scanner.pos)),
  )
}

///|
/// Try to parse image
fn InlineParser::try_parse_image(self : InlineParser, start : Int) -> Inline? {
  self.scanner.advance(1) // Skip !
  if !char_is(self.scanner.peek(), '[') {
    self.scanner.restore(start)
    return None
  }
  self.scanner.advance(1) // Skip [

  // Parse alt text
  let alt_buf = StringBuilder::new()
  while !self.scanner.is_eof() {
    match self.scanner.peek() {
      Some(']') => {
        self.scanner.advance(1)
        break
      }
      Some('\\') => {
        self.scanner.advance(1)
        match self.scanner.consume() {
          Some(c) => alt_buf.write_char(c)
          None => break
        }
      }
      Some(c) => {
        alt_buf.write_char(c)
        self.scanner.advance(1)
      }
      None => break
    }
  }
  let alt = alt_buf.to_string()

  // Check for inline image (url)
  if char_is(self.scanner.peek(), '(') {
    self.scanner.advance(1)

    // Parse URL (same as link)
    let url_buf = StringBuilder::new()
    let _ = self.scanner.skip_spaces()
    if char_is(self.scanner.peek(), '<') {
      self.scanner.advance(1)
      while !self.scanner.is_eof() {
        match self.scanner.peek() {
          Some('>') => {
            self.scanner.advance(1)
            break
          }
          Some(c) => {
            url_buf.write_char(c)
            self.scanner.advance(1)
          }
          None => break
        }
      }
    } else {
      while !self.scanner.is_eof() {
        match self.scanner.peek() {
          Some(')') | Some(' ') | Some('\t') => break
          Some(c) => {
            url_buf.write_char(c)
            self.scanner.advance(1)
          }
          None => break
        }
      }
    }
    let url = url_buf.to_string()
    let _ = self.scanner.skip_spaces()

    // Parse optional title
    let title = match self.scanner.peek() {
      Some('"') | Some('\'') => {
        let quote = match self.scanner.consume() {
          Some(c) => c
          None => '"'
        }
        let title_buf = StringBuilder::new()
        while !self.scanner.is_eof() {
          match self.scanner.peek() {
            Some(c) if c == quote => {
              self.scanner.advance(1)
              break
            }
            Some(c) => {
              title_buf.write_char(c)
              self.scanner.advance(1)
            }
            None => break
          }
        }
        title_buf.to_string()
      }
      _ => ""
    }
    let _ = self.scanner.skip_spaces()
    if char_is(self.scanner.peek(), ')') {
      self.scanner.advance(1)
      return Some(
        Inline::Image(
          alt~,
          url~,
          title~,
          span=Span::new(start, self.scanner.pos),
        ),
      )
    }
  }

  // Check for reference image ![alt][ref]
  if char_is(self.scanner.peek(), '[') {
    self.scanner.advance(1)
    let label_buf = StringBuilder::new()
    while !self.scanner.is_eof() {
      match self.scanner.peek() {
        Some(']') => {
          self.scanner.advance(1)
          break
        }
        Some(c) => {
          label_buf.write_char(c)
          self.scanner.advance(1)
        }
        None => break
      }
    }
    return Some(
      Inline::RefImage(
        alt~,
        label=label_buf.to_string(),
        span=Span::new(start, self.scanner.pos),
      ),
    )
  }
  self.scanner.restore(start)
  None
}