import type { StreamParser, StringStream } from '@codemirror/language';

export type SimpleLanguageSpec = {
  keywords?: ReadonlySet<string>;
  types?: ReadonlySet<string>;
  lineComment?: string;
  hashComment?: boolean;
  blockComment?: readonly [string, string];
  variablePrefix?: RegExp;
};

type State = { blockComment: boolean };

export function wordSet(words: string): ReadonlySet<string> {
  return new Set(words.trim().split(/\s+/u).filter(Boolean));
}

function eatQuoted(stream: StringStream, quote: string): void {
  let escaped = false;
  while (!stream.eol()) {
    const ch = stream.next();
    if (ch === quote && !escaped) break;
    escaped = ch === '\\' && !escaped;
    if (ch !== '\\') escaped = false;
  }
}

export function simpleParser(spec: SimpleLanguageSpec): StreamParser<State> {
  return {
    startState: () => ({ blockComment: false }),
    token(stream, state) {
      if (state.blockComment && spec.blockComment) {
        if (stream.skipTo(spec.blockComment[1])) {
          stream.match(spec.blockComment[1]);
          state.blockComment = false;
        } else stream.skipToEnd();
        return 'comment';
      }
      if (stream.eatSpace()) return null;
      if (spec.lineComment && stream.match(spec.lineComment)) { stream.skipToEnd(); return 'comment'; }
      if (spec.hashComment && stream.peek() === '#') { stream.skipToEnd(); return 'comment'; }
      if (spec.blockComment && stream.match(spec.blockComment[0])) {
        if (!stream.skipTo(spec.blockComment[1])) { state.blockComment = true; stream.skipToEnd(); }
        else stream.match(spec.blockComment[1]);
        return 'comment';
      }
      const quote = stream.peek();
      if (quote === '"' || quote === "'" || quote === '`') { stream.next(); eatQuoted(stream, quote); return 'string'; }
      if (stream.match(/^(?:0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/iu)) return 'number';
      if (spec.variablePrefix && stream.match(spec.variablePrefix)) return 'variableName';
      if (stream.match(/^[A-Za-z_$][\w$-]*/u)) {
        const word = stream.current();
        if (spec.keywords?.has(word)) return 'keyword';
        if (spec.types?.has(word)) return 'typeName';
        return 'variableName';
      }
      if (stream.match(/^[{}()[\],.;:]/u)) return 'punctuation';
      if (stream.match(/^[+\-*/%=&|!<>?~^]+/u)) return 'operator';
      stream.next();
      return null;
    },
  };
}
