/// <reference types="tree-sitter-cli/dsl" />
// @ts-check

/**
 * Tree-sitter grammar for Wasot markup language
 *
 * Parses:
 * - Meta-lines: .type, ..type (nested), .type: (void)
 * - Block structure: type, inline text, parameters, IDs
 * - Text content lines
 * - Context lines: ./type
 */

module.exports = grammar({
  name: "wasot",

  extras: ($) => [],

  rules: {
    source_file: ($) => repeat(choice($.meta_line, $.text_line)),

    // Meta-line: entire line starting with dots
    meta_line: ($) =>
      choice(
        // Context line: ./something
        seq($.dots, $.context_marker, optional($.context_content), optional("\n")),
        // Void element with type: .type:
        seq($.dots, $.block_type, $.void_marker, optional($.void_content), optional("\n")),
        // Void element without type: .:
        seq($.dots, $.void_marker, optional($.void_content), optional("\n")),
        // Regular block with type: .type with optional content
        seq($.dots, $.block_type, optional($.block_content), optional("\n")),
        // Empty block (just dots) - must come last as fallback
        seq($.dots, optional("\n"))
      ),

    // Context marker: / immediately after dots
    context_marker: ($) => token.immediate(/\//),

    // One or more dots indicating nesting level
    dots: ($) => /\.+/,

    // Block type immediately after dots
    block_type: ($) =>
      token.immediate(
        choice(
          // Heading: #, ##, ###, etc
          /#+/,
          // Quoted type: "my type" (properly closed)
          /"([^"\\]|\\.)*"/,
          // Unclosed quoted type: "word (quote followed by non-space chars until space/newline)
          /"[^\s]+/,
          // Simple type: identifier (can include emojis via unicode)
          // Matches any non-whitespace, non-special chars
          /[^\s:\/\."]+/
        )
      ),

    // Void marker: the colon that marks a void element
    void_marker: ($) => token.immediate(/:/),

    // Content after context line ./
    context_content: ($) => token.immediate(/[^\n]+/),

    // Content after void element .type: (space + content)
    void_content: ($) => seq($.space, $.content_parts),

    // Content after block type (space + content)
    // Use token.immediate to ensure we capture content that follows block_type
    block_content: ($) => token.immediate(/ [^\n]*/),

    // Space token (used by void_content)
    space: ($) => token.immediate(/ /),

    // Content parts: sequence of tokens until end of line
    content_parts: ($) =>
      repeat1(
        choice(
          $.param_separator,
          $.block_id,
          $.kwarg,
          $.quoted_string,
          $.unclosed_quote,
          $.word,
          $.extra_space
        )
      ),

    // Extra spaces between content parts
    extra_space: ($) => / +/,

    // Parameter separator: two dots (with optional space after)
    param_separator: ($) => prec.left(seq($._dot_dot, optional($.extra_space))),

    // Two dots token
    _dot_dot: ($) => token(prec(10, /\.\./)),

    // Block ID: colon followed immediately by identifier (as single token)
    // This ensures lone colons are treated as words
    block_id: ($) => token(prec(5, /:[a-zA-Z0-9_\-\u0080-\uFFFF]+/)),

    // Keyword argument: key=value as a single token
    // We match the whole thing to avoid lookahead issues
    kwarg: ($) =>
      token(
        prec(
          5,
          choice(
            // "key"="value"
            /"([^"\\]|\\.)*"="([^"\\]|\\.)*"/,
            // "key"=value
            /"([^"\\]|\\.)*"=[^\s]+/,
            // key="value"
            /[a-zA-Z_][a-zA-Z0-9_\-]*="([^"\\]|\\.)*"/,
            // key=value
            /[a-zA-Z_][a-zA-Z0-9_\-]*=[^\s]+/
          )
        )
      ),

    // Quoted string (standalone, not part of kwarg)
    quoted_string: ($) => token(prec(3, /"([^"\\]|\\.)*"/)),

    // Unclosed quote: quote followed by any chars until end of line (fallback)
    unclosed_quote: ($) => token(prec(2, /"[^"\n]*/)),

    // Regular word (not an ID, not a kwarg key)
    // Includes colons that aren't followed by identifier (block_id has higher prec)
    word: ($) => token(prec(1, /[^\s="]+/)),

    // Text-line: any line not starting with a dot (or empty line)
    // Low precedence so meta_line components take priority
    text_line: ($) => token(prec(-1, /[^.\n][^\n]*\n?|\n/)),
  },
});
