#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const long long mod = 998244353;
struct DSU {
  vector<int> parent, nxt;
  DSU() {}
  DSU(int n) {
    nxt.resize(n);
    parent.resize(n);
    for (int i = 0; i < n; i++) {
      nxt[i] = i + 1;
      parent[i] = i;
    }
  }
  int Find(int x) {
    if (parent[x] == x) return x;
    parent[x] = Find(parent[x]);
    return parent[x];
  }
  void Union(int u, int v) {
    u = Find(u);
    v = Find(v);
    if (u == v) return;
    nxt[u] = max(nxt[u], nxt[v]);
    parent[v] = u;
  }
  int getNxt(int x) { return nxt[Find(x)]; }
};
struct segTree {
  long long val;
  long long a, b;
  int l, r;
  segTree *L, *R;
  segTree() {}
  segTree(int l, int r) {
    this->val = 0;
    this->a = 1;
    this->b = 0;
    this->l = l;
    this->r = r;
    this->L = nullptr;
    this->R = nullptr;
  }
  void build() {
    if (this->l == this->r) {
      return;
    }
    this->L = new segTree(this->l, (this->l + this->r) / 2);
    this->R = new segTree((this->l + this->r) / 2 + 1, this->r);
    this->L->build();
    this->R->build();
  }
  void updateLazy() {
    this->val =
        (this->a * this->val + (this->b * (this->r - this->l + 1) % mod)) % mod;
    if (this->l != this->r) {
      this->L->a = (this->L->a * this->a) % mod;
      this->L->b = (this->L->b * this->a + this->b) % mod;
      this->R->a = (this->R->a * this->a) % mod;
      this->R->b = (this->R->b * this->a + this->b) % mod;
    }
    this->a = 1;
    this->b = 0;
  }
  long long query(int ql, int qr) {
    this->updateLazy();
    if (this->l >= ql && this->r <= qr) return this->val;
    if (this->r < ql || this->l > qr) return 0;
    return (this->L->query(ql, qr) + this->R->query(ql, qr)) % mod;
  }
  void update(int ql, int qr, long long mul, long long add) {
    if (ql > qr) return;
    this->updateLazy();
    if (this->l >= ql && this->r <= qr) {
      this->val =
          (mul * this->val + (add * (this->r - this->l + 1) % mod)) % mod;
      if (this->l != this->r) {
        this->L->a = (this->L->a * mul) % mod;
        this->L->b = (this->L->b * mul + add) % mod;
        this->R->a = (this->R->a * mul) % mod;
        this->R->b = (this->R->b * mul + add) % mod;
      }
      return;
    }
    if (this->r < ql || this->l > qr) return;
    this->L->update(ql, qr, mul, add);
    this->R->update(ql, qr, mul, add);
    this->val = (this->L->val + this->R->val) % mod;
  }
};
struct query {
  int type;
  int l, r, x;
  query() {}
  query(int l, int r) {
    this->type = 2;
    this->l = l;
    this->r = r;
  }
  query(int l, int r, int x) {
    this->type = 1;
    this->l = l;
    this->r = r;
    this->x = x;
  }
};
int n, Q;
query q[MAXN];
segTree *T;
DSU opt[MAXN];
set<int> borders[MAXN];
vector<bool> used[MAXN];
vector<pair<int, int>> segments[MAXN];
void doUpdate(int l, int r, int x) {
  vector<int> add, mul;
  int ind =
      lower_bound(segments[x].begin(), segments[x].end(), make_pair(l, -1)) -
      segments[x].begin();
  for (; ind < segments[x].size(); ind = opt[x].getNxt(ind)) {
    if (segments[x][ind].first > r) break;
    if (used[x][ind] == false) {
      add.push_back(ind);
    }
  }
  for (int ind : add) {
    T->update(segments[x][ind].first, segments[x][ind].second, 1, 1);
  }
  mul.push_back(l);
  for (int ind : add) {
    mul.push_back(segments[x][ind].first - 1);
    mul.push_back(segments[x][ind].second + 1);
  }
  mul.push_back(r);
  for (int i = 0; i < mul.size(); i += 2) {
    if (mul[i] <= mul[i + 1]) {
      T->update(mul[i], mul[i + 1], 2, 0);
    }
  }
  for (int ind : add) {
    used[x][ind] = true;
    if (ind != 0 && used[x][ind - 1] == true) opt[x].Union(ind - 1, ind);
    if (ind != used[x].size() - 1 && used[x][ind + 1] == true)
      opt[x].Union(ind, ind + 1);
  }
}
int main() {
  ios::sync_with_stdio(false);
  cin.tie(NULL);
  cin >> n >> Q;
  T = new segTree(0, n - 1);
  T->build();
  for (int i = 0; i < Q; i++) {
    int type;
    cin >> type;
    if (type == 1) {
      int l, r, x;
      cin >> l >> r >> x;
      l--;
      r--;
      q[i] = query(l, r, x);
      borders[x].insert(l);
      borders[x].insert(r);
    } else {
      int l, r;
      cin >> l >> r;
      l--;
      r--;
      q[i] = query(l, r);
    }
  }
  for (int x = 1; x <= n; x++) {
    for (auto it = borders[x].begin(); it != borders[x].end(); it++) {
      segments[x].push_back({*it, *it});
      auto it1 = it;
      it1++;
      if (it != borders[x].end() && *it1 - *it > 1) {
        segments[x].push_back({*it + 1, *it1 - 1});
      }
    }
    opt[x] = DSU(segments[x].size());
    used[x].assign(segments[x].size(), false);
  }
  for (int i = 0; i < Q; i++) {
    if (q[i].type == 1) {
      doUpdate(q[i].l, q[i].r, q[i].x);
    } else {
      cout << T->query(q[i].l, q[i].r) << '\n';
    }
  }
}
