/**
 * Tree-sitter grammar for Storybook DSL
 *
 * This grammar defines the syntax for the Storybook narrative DSL,
 * including characters, templates, relationships, life arcs, behaviors, etc.
 */

module.exports = grammar({
  name: 'storybook',

  // externals: $ => [
  //   $._prose_block_content,
  //   $._prose_block_end
  // ],

  extras: $ => [
    /\s/,  // Whitespace
    $.line_comment,
    $.block_comment
  ],

  conflicts: $ => [
    [$.path_segments],
    [$.sub_concept_enum_body, $.sub_concept_record_body]
  ],

  word: $ => $.identifier,

  rules: {
    // Top-level structure
    source_file: $ => repeat($.declaration),

    // Comments
    line_comment: $ => token(seq('//', /.*/)),
    block_comment: $ => token(seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/')),

    // Declarations
    declaration: $ => choice(
      $.use_declaration,
      $.character,
      $.template,
      $.life_arc,
      $.schedule,
      $.behavior,
      $.institution,
      $.relationship,
      $.location,
      $.species,
      $.enum_declaration,
      $.concept_declaration,
      $.sub_concept,
      $.concept_comparison
    ),

    // Use declarations
    use_declaration: $ => seq(
      'use',
      $.path_segments,
      optional(choice(
        seq('::', '{', commaSep1($.identifier), '}'),
        seq('::', '*')
      )),
      ';'
    ),

    path: $ => $.path_segments,

    path_segments: $ => sep1($.identifier, token('::')),

    // Character declaration
    character: $ => seq(
      'character',
      field('name', $.identifier),
      optional(seq(':', field('species', $.identifier))),
      optional(field('template', $.template_clause)),
      field('body', $.block)
    ),

    template_clause: $ => seq('from', commaSep1($.identifier)),

    // Template declaration
    template: $ => seq(
      'template',
      field('name', $.identifier),
      optional(seq(':', field('species', $.identifier))),
      optional('strict'),
      '{',
      repeat($.include),
      repeat($.field),
      '}'
    ),

    include: $ => seq('include', $.identifier),

    // Fields (key: value pairs)
    field: $ => choice(
      seq(
        field('name', $.dotted_path),
        ':',
        field('value', $.value)
      ),
      $.prose_block
    ),

    dotted_path: $ => sep1($.identifier, '.'),

    // Values
    value: $ => choice(
      $.integer,
      $.float,
      $.string,
      $.boolean,
      $.range,
      $.time,
      $.duration,
      $.path,
      $.prose_block,
      $.list,
      $.object,
      $.override
    ),

    integer: $ => /-?[0-9]+/,

    float: $ => /-?[0-9]+\.[0-9]+/,

    string: $ => /"([^"\\]|\\.)*"/,

    boolean: $ => choice('true', 'false'),

    range: $ => choice(
      seq($.integer, '..', $.integer),
      seq($.float, '..', $.float)
    ),

    time: $ => /[0-9]{2}:[0-9]{2}(:[0-9]{2})?/,

    duration: $ => /[0-9]+[dhms]([0-9]+[dhms])*/,

    list: $ => seq('[', commaSep($.value), ']'),

    object: $ => $.block,

    block: $ => seq('{', repeat($.field), '}'),

    // Override (@base { remove field, field: value })
    override: $ => seq(
      '@',
      $.path,
      '{',
      repeat($.override_op),
      '}'
    ),

    override_op: $ => choice(
      seq('remove', $.identifier),
      seq('append', $.field),
      $.field
    ),

    // Prose blocks (---tag content ---)
    prose_block: $ => seq(
      field('marker', $.prose_marker),
      field('tag', $.identifier),
      optional(/[^\n]*/), // Rest of opening line
      field('content', $.prose_content),
      field('end', $.prose_marker)
    ),

    prose_marker: $ => '---',

    // Capture prose content as a single token for markdown injection
    prose_content: $ => token(prec(-1, repeat1(choice(
      /[^\-]+/,  // Any non-dash characters
      /-[^\-]/,  // Single dash not followed by another dash
      /-\-[^\-]/,  // Two dashes not followed by another dash
    )))),

    // Life arc declaration
    life_arc: $ => seq(
      'life_arc',
      field('name', $.identifier),
      '{',
      repeat($.field),
      repeat($.arc_state),
      '}'
    ),

    arc_state: $ => seq(
      'state',
      field('name', $.identifier),
      '{',
      optional($.on_enter),
      repeat($.field),
      repeat($.transition),
      '}'
    ),

    on_enter: $ => seq('on', 'enter', $.block),

    transition: $ => seq(
      'on',
      field('condition', $.expression),
      '->',
      field('target', $.identifier)
    ),

    // Schedule declaration
    schedule: $ => seq(
      'schedule',
      field('name', $.identifier),
      '{',
      repeat($.field),
      repeat($.schedule_block),
      '}'
    ),

    schedule_block: $ => seq(
      field('start', $.time),
      '->',
      field('end', $.time),
      ':',
      field('activity', $.identifier),
      $.block
    ),

    // Behavior tree declaration
    behavior: $ => seq(
      'behavior',
      field('name', $.identifier),
      '{',
      repeat($.field),
      field('root', $.behavior_node),
      '}'
    ),

    behavior_node: $ => choice(
      $.selector_node,
      $.sequence_node,
      $.condition_node,
      $.if_decorator_node,
      $.decorator_node,
      $.action_node,
      $.subtree_node
    ),

    // Selector node: choose { ... }
    selector_node: $ => seq(
      'choose',
      optional(field('label', $.identifier)),
      '{',
      repeat1($.behavior_node),
      '}'
    ),

    // Sequence node: then { ... }
    sequence_node: $ => seq(
      'then',
      optional(field('label', $.identifier)),
      '{',
      repeat1($.behavior_node),
      '}'
    ),

    // Condition node: if(expr) or when(expr) - NO BRACES
    condition_node: $ => seq(
      choice('if', 'when'),
      '(',
      field('condition', $.expression),
      ')'
    ),

    // If decorator: if(expr) { child } - WITH BRACES
    if_decorator_node: $ => seq(
      'if',
      '(',
      field('condition', $.expression),
      ')',
      '{',
      field('child', $.behavior_node),
      '}'
    ),

    // Decorator node: repeat/retry/timeout/etc { child }
    decorator_node: $ => seq(
      field('decorator', $.decorator_keyword),
      optional(field('params', $.decorator_params)),
      '{',
      field('child', $.behavior_node),
      '}'
    ),

    decorator_keyword: $ => choice(
      'repeat',
      'invert',
      'retry',
      'timeout',
      'cooldown',
      'succeed_always',
      'fail_always'
    ),

    decorator_params: $ => seq(
      '(',
      choice(
        // min..max range (for repeat)
        seq($.integer, '..', $.integer),
        // N (for repeat, retry)
        $.integer,
        // duration (for timeout, cooldown)
        $.duration
      ),
      ')'
    ),

    // Action node: action_name or action_name(params)
    action_node: $ => choice(
      seq($.identifier, '(', commaSep($.action_param), ')'),
      $.identifier
    ),

    action_param: $ => choice(
      // Named parameter: name: value
      seq($.dotted_path, ':', $.value),
      // Positional parameter: just value
      $.value
    ),

    // Subtree node: include path::to::subtree
    subtree_node: $ => seq('include', $.path),

    // Institution declaration
    institution: $ => seq(
      'institution',
      field('name', $.identifier),
      $.block
    ),

    // Relationship declaration
    relationship: $ => seq(
      'relationship',
      field('name', $.identifier),
      '{',
      repeat1($.participant),
      repeat($.field),
      '}'
    ),

    participant: $ => choice(
      // name as role { fields } (role optional)
      seq($.path, 'as', $.identifier, $.block),
      // name { fields } (block required)
      seq($.path, $.block)
    ),

    // Location declaration
    location: $ => seq(
      'location',
      field('name', $.identifier),
      $.block
    ),

    // Species declaration
    species: $ => seq(
      'species',
      field('name', $.identifier),
      '{',
      repeat($.include),
      repeat($.field),
      '}'
    ),

    // Enum declaration
    enum_declaration: $ => seq(
      'enum',
      field('name', $.identifier),
      '{',
      commaSep1($.identifier),
      '}'
    ),

    // Concept declaration - base type with no structure
    concept_declaration: $ => seq(
      'concept',
      field('name', $.identifier),
      ';'
    ),

    // Sub-concept declaration - enum or record subtype with dot notation
    sub_concept: $ => seq(
      'sub_concept',
      field('parent', $.identifier),
      '.',
      field('name', $.identifier),
      '{',
      field('body', choice(
        $.sub_concept_record_body,
        $.sub_concept_enum_body
      )),
      '}'
    ),

    // Enum form: Variant1, Variant2, Variant3
    sub_concept_enum_body: $ => commaSep1($.identifier),

    // Record form: field_name: Type, field_name: any
    sub_concept_record_body: $ => commaSep1($.sub_concept_field),

    sub_concept_field: $ => seq(
      field('name', $.identifier),
      ':',
      field('type', choice($.identifier, $.any_type))
    ),

    any_type: $ => 'any',

    // Concept comparison - compile-time pattern matching
    concept_comparison: $ => seq(
      'concept_comparison',
      field('name', $.identifier),
      '{',
      commaSep1($.variant_pattern),
      '}'
    ),

    // A named variant with field conditions
    variant_pattern: $ => seq(
      field('name', $.identifier),
      ':',
      '{',
      commaSep1($.field_condition),
      '}'
    ),

    // A condition on a sub_concept field
    field_condition: $ => seq(
      field('sub_concept', $.dotted_path),
      ':',
      field('condition', $.condition_expr)
    ),

    // Condition: either 'any' or 'Type is Value or Type is Value2'
    condition_expr: $ => choice(
      $.any_type,
      $.is_condition
    ),

    // Pattern: DottedPath is Ident [or DottedPath is Ident]*
    is_condition: $ => seq(
      $.dotted_path,
      'is',
      $.identifier,
      repeat(seq('or', $.dotted_path, 'is', $.identifier))
    ),

    // Expressions (for conditions in life arcs)
    expression: $ => choice(
      $.or_expression,
      $.and_expression,
      $.not_expression,
      $.comparison,
      $.field_access,
      $.primary_expression
    ),

    or_expression: $ => prec.left(1, seq(
      $.expression,
      'or',
      $.expression
    )),

    and_expression: $ => prec.left(2, seq(
      $.expression,
      'and',
      $.expression
    )),

    not_expression: $ => prec(3, seq('not', $.expression)),

    comparison: $ => prec.left(4, seq(
      $.expression,
      field('operator', choice('is', '>', '>=', '<', '<=')),
      $.expression
    )),

    field_access: $ => prec.left(5, seq(
      $.expression,
      '.',
      $.identifier
    )),

    primary_expression: $ => choice(
      'self',
      'other',
      $.integer,
      $.float,
      $.string,
      $.boolean,
      $.path
    ),

    // Identifiers
    identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*/,
  }
});

/**
 * Helper to create comma-separated lists with optional trailing comma
 */
function commaSep(rule) {
  return optional(commaSep1(rule));
}

function commaSep1(rule) {
  return seq(rule, repeat(seq(',', rule)), optional(','));
}

/**
 * Helper to create separator-based lists (e.g., :: or .)
 */
function sep1(rule, separator) {
  return seq(rule, repeat(seq(separator, rule)));
}
