// MNote 文件树上下文菜单运行时外置模块。
// 当前先承接 CommandContext 构造与 when 表达式求值，树语义仍由 Rust/kernel 主导。

function buildSidebarFileTreeContext(kind, deps) {
  var selection = deps && deps.selection ? deps.selection : {};
  var rowIds = Array.isArray(selection.selectedRowIds)
    ? selection.selectedRowIds
    : Array.from(selection.selectedRowIds || []);
  var rows = [];
  var queryRowById = deps && typeof deps.queryRowById === 'function'
    ? deps.queryRowById
    : function() { return null; };
  for (var i = 0; i < rowIds.length; i += 1) {
    var row = queryRowById(rowIds[i]);
    if (row instanceof HTMLElement) rows.push(row);
  }
  var resourceKinds = {};
  for (var j = 0; j < rows.length; j += 1) {
    var rk = rows[j].getAttribute('data-row-kind') || 'unknown';
    resourceKinds[rk] = true;
  }
  var rkKeys = Object.keys(resourceKinds);
  var currentSourceKind = deps && typeof deps.currentSourceKind === 'function'
    ? deps.currentSourceKind
    : function() { return ''; };
  var readonly = deps && typeof deps.workspaceReadonly === 'function'
    ? deps.workspaceReadonly()
    : false;
  return {
    'workspace.sourceKind': currentSourceKind() || '',
    'workspace.readonly': readonly === true,
    'tree.focusKind': kind === 'filetree' ? 'file_tree' : kind === 'page' ? 'page_tree' : kind,
    'tree.selectionCount': rows.length,
    'tree.selectionResourceKind': rkKeys.length === 1 ? rkKeys[0] : 'mixed'
  };
}

function evaluateSidebarFileTreeWhen(ctx, expr) {
  if (!expr) return true;
  try {
    expr = String(expr || '').trim();
    var tokens = tokenizeWhenExpression(expr);
    var pos = 0;
    function peek() { return tokens[pos]; }
    function consume() { return tokens[pos++]; }
    function expect(t) {
      var tok = consume();
      if (!tok || tok.t !== t) throw new Error('Expected ' + t + ' got ' + (tok ? tok.t : 'EOF'));
      return tok;
    }
    function ctxVal(key) {
      var v = ctx[key];
      if (v === undefined || v === null) return '';
      return v;
    }
    function compareValue(cv, value, equal) {
      if (value === 'true') {
        var truth = cv === true || cv === 'true';
        return equal ? truth : !truth;
      }
      if (value === 'false') {
        var falsy = cv === false || cv === '' || cv === 'false';
        return equal ? falsy : !falsy;
      }
      var num = Number(value);
      if (!isNaN(num) && String(num) === value) {
        return equal ? Number(cv) === num : Number(cv) !== num;
      }
      return equal ? String(cv) === value : String(cv) !== value;
    }
    function parseOr() {
      var left = parseAnd();
      while (peek() && peek().t === '||') {
        consume();
        var right = parseAnd();
        left = left || right;
      }
      return left;
    }
    function parseAnd() {
      var left = parsePrimary();
      while (peek() && peek().t === '&&') {
        consume();
        var right = parsePrimary();
        left = left && right;
      }
      return left;
    }
    function parsePrimary() {
      if (!peek()) throw new Error('Unexpected EOF');
      if (peek().t === '(') {
        consume();
        var grouped = parseOr();
        expect(')');
        return grouped;
      }
      if (peek().t === '!') {
        consume();
        var notTok = consume();
        if (!notTok || notTok.t !== 'id') throw new Error('Expected key after !');
        return !ctxVal(notTok.v);
      }
      if (peek().t === 'id') {
        var keyTok = consume();
        if (peek() && peek().t === '==') {
          consume();
          var eqTok = consume();
          if (!eqTok) throw new Error('Expected value after ==');
          return compareValue(ctxVal(keyTok.v), eqTok.t === 'str' ? eqTok.v : (eqTok.v || ''), true);
        }
        if (peek() && peek().t === '!=') {
          consume();
          var neTok = consume();
          if (!neTok) throw new Error('Expected value after !=');
          return compareValue(ctxVal(keyTok.v), neTok.t === 'str' ? neTok.v : (neTok.v || ''), false);
        }
        var cv = ctxVal(keyTok.v);
        if (typeof cv === 'boolean') return cv;
        if (typeof cv === 'number') return cv !== 0;
        return cv !== '' && cv !== undefined && cv !== null;
      }
      throw new Error('Unexpected token: ' + peek().t);
    }
    return parseOr();
  } catch (_) {
    return true;
  }
}

function tokenizeWhenExpression(expr) {
  var tokens = [];
  var i = 0;
  while (i < expr.length) {
    var ch = expr[i];
    if (ch === ' ' || ch === '\t') { i += 1; continue; }
    if (ch === '(') { tokens.push({ t: '(' }); i += 1; continue; }
    if (ch === ')') { tokens.push({ t: ')' }); i += 1; continue; }
    if (ch === '&' && expr[i + 1] === '&') { tokens.push({ t: '&&' }); i += 2; continue; }
    if (ch === '|' && expr[i + 1] === '|') { tokens.push({ t: '||' }); i += 2; continue; }
    if (ch === '=' && expr[i + 1] === '=') { tokens.push({ t: '==' }); i += 2; continue; }
    if (ch === '!' && expr[i + 1] === '=') { tokens.push({ t: '!=' }); i += 2; continue; }
    if (ch === '!') { tokens.push({ t: '!' }); i += 1; continue; }
    if (ch === '"' || ch === "'") {
      i += 1;
      var str = '';
      while (i < expr.length && expr[i] !== ch) {
        str += expr[i];
        i += 1;
      }
      if (i < expr.length) i += 1;
      tokens.push({ t: 'str', v: str });
      continue;
    }
    if (
      (ch >= 'a' && ch <= 'z') ||
      (ch >= 'A' && ch <= 'Z') ||
      (ch >= '0' && ch <= '9') ||
      ch === '_' ||
      ch === '.'
    ) {
      var id = '';
      while (
        i < expr.length &&
        (
          (expr[i] >= 'a' && expr[i] <= 'z') ||
          (expr[i] >= 'A' && expr[i] <= 'Z') ||
          (expr[i] >= '0' && expr[i] <= '9') ||
          expr[i] === '_' ||
          expr[i] === '.'
        )
      ) {
        id += expr[i];
        i += 1;
      }
      tokens.push({ t: 'id', v: id });
      continue;
    }
    throw new Error('Unexpected char: ' + ch);
  }
  return tokens;
}

window.__mnoteFileTreeContextMenuRuntime = {
  buildSidebarFileTreeContext: buildSidebarFileTreeContext,
  evaluateSidebarFileTreeWhen: evaluateSidebarFileTreeWhen,
  tokenizeWhenExpression: tokenizeWhenExpression
};
