import csharp from './csharp.mjs';
import markup from './markup.mjs';

// @ts-nocheck
/**
 * @import {Refractor} from '../lib/core.js'
 */
cshtml.displayName = 'cshtml';
cshtml.aliases = ['razor'];

/** @param {Refractor} Prism */
function cshtml(Prism) {
  Prism.register(csharp);
  Prism.register(markup)
  // Docs:
  // https://docs.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-5.0&tabs=visual-studio
  // https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-5.0

  ;(function (Prism) {
    var commentLike = /\/(?![/*])|\/\/.*[\r\n]|\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\//
      .source;
    var stringLike =
      /@(?!")|"(?:[^\r\n\\"]|\\.)*"|@"(?:[^\\"]|""|\\[\s\S])*"(?!")/.source +
      '|' +
      /'(?:(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'|(?=[^\\](?!')))/.source;

    /**
     * Creates a nested pattern where all occurrences of the string `<<self>>` are replaced with the pattern itself.
     *
     * @param {string} pattern
     * @param {number} depthLog2
     * @returns {string}
     */
    function nested(pattern, depthLog2) {
      for (var i = 0; i < depthLog2; i++) {
        pattern = pattern.replace(/<self>/g, function () {
          return '(?:' + pattern + ')'
        });
      }
      return pattern
        .replace(/<self>/g, '[^\\s\\S]')
        .replace(/<str>/g, '(?:' + stringLike + ')')
        .replace(/<comment>/g, '(?:' + commentLike + ')')
    }
    var round = nested(/\((?:[^()'"@/]|<str>|<comment>|<self>)*\)/.source, 2);
    var square = nested(/\[(?:[^\[\]'"@/]|<str>|<comment>|<self>)*\]/.source, 1);
    var curly = nested(/\{(?:[^{}'"@/]|<str>|<comment>|<self>)*\}/.source, 2);
    var angle = nested(/<(?:[^<>'"@/]|<comment>|<self>)*>/.source, 1);
    var inlineCs =
      /@/.source +
      /(?:await\b\s*)?/.source +
      '(?:' +
      /(?!await\b)\w+\b/.source +
      '|' +
      round +
      ')' +
      '(?:' +
      /[?!]?\.\w+\b/.source +
      '|' +
      '(?:' +
      angle +
      ')?' +
      round +
      '|' +
      square +
      ')*' +
      /(?![?!\.(\[]|<(?!\/))/.source;

    // Note about the above bracket patterns:
    // They all ignore HTML expressions that might be in the C# code. This is a problem because HTML (like strings and
    // comments) is parsed differently. This is a huge problem because HTML might contain brackets and quotes which
    // messes up the bracket and string counting implemented by the above patterns.
    //
    // This problem is not fixable because 1) HTML expression are highly context sensitive and very difficult to detect
    // and 2) they require one capturing group at every nested level. See the `tagRegion` pattern to admire the
    // complexity of an HTML expression.
    //
    // To somewhat alleviate the problem a bit, the patterns for characters (e.g. 'a') is very permissive, it also
    // allows invalid characters to support HTML expressions like this: <p>That's it!</p>.

    var tagAttrInlineCs = /@(?![\w()])/.source + '|' + inlineCs;
    var tagAttrValue =
      '(?:' +
      /"[^"@]*"|'[^'@]*'|[^\s'"@>=]+(?=[\s>])/.source +
      '|' +
      '["\'][^"\'@]*(?:(?:' +
      tagAttrInlineCs +
      ')[^"\'@]*)+["\']' +
      ')';
    var tagAttrs =
      /(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*<tagAttrValue>|(?=[\s/>])))+)?/.source.replace(
        /<tagAttrValue>/,
        tagAttrValue
      );
    var tagContent = /(?!\d)[^\s>\/=$<%]+/.source + tagAttrs + /\s*\/?>/.source;
    var tagRegion =
      /\B@?/.source +
      '(?:' +
      /<([a-zA-Z][\w:]*)/.source +
      tagAttrs +
      /\s*>/.source +
      '(?:' +
      (/[^<]/.source +
        '|' +
        // all tags that are not the start tag
        // eslint-disable-next-line regexp/strict
        /<\/?(?!\1\b)/.source +
        tagContent +
        '|' +
        // nested start tag
        nested(
          // eslint-disable-next-line regexp/strict
          /<\1/.source +
            tagAttrs +
            /\s*>/.source +
            '(?:' +
            (/[^<]/.source +
              '|' +
              // all tags that are not the start tag
              // eslint-disable-next-line regexp/strict
              /<\/?(?!\1\b)/.source +
              tagContent +
              '|' +
              '<self>') +
            ')*' +
            // eslint-disable-next-line regexp/strict
            /<\/\1\s*>/.source,
          2
        )) +
      ')*' +
      // eslint-disable-next-line regexp/strict
      /<\/\1\s*>/.source +
      '|' +
      /</.source +
      tagContent +
      ')';

    // Now for the actual language definition(s):
    //
    // Razor as a language has 2 parts:
    //  1) CSHTML: A markup-like language that has been extended with inline C# code expressions and blocks.
    //  2) C#+HTML: A variant of C# that can contain CSHTML tags as expressions.
    //
    // In the below code, both CSHTML and C#+HTML will be create as separate language definitions that reference each
    // other. However, only CSHTML will be exported via `Prism.languages`.

    Prism.languages.cshtml = Prism.languages.extend('markup', {});
    var csharpWithHtml = Prism.languages.insertBefore(
      'csharp',
      'string',
      {
        html: {
          pattern: RegExp(tagRegion),
          greedy: true,
          inside: Prism.languages.cshtml
        }
      },
      {
        csharp: Prism.languages.extend('csharp', {})
      }
    );
    var cs = {
      pattern: /\S[\s\S]*/,
      alias: 'language-csharp',
      inside: csharpWithHtml
    };
    var inlineValue = {
      pattern: RegExp(/(^|[^@])/.source + inlineCs),
      lookbehind: true,
      greedy: true,
      alias: 'variable',
      inside: {
        keyword: /^@/,
        csharp: cs
      }
    };
    Prism.languages.cshtml.tag.pattern = RegExp(/<\/?/.source + tagContent);
    Prism.languages.cshtml.tag.inside['attr-value'].pattern = RegExp(
      /=\s*/.source + tagAttrValue
    );
    Prism.languages.insertBefore(
      'inside',
      'punctuation',
      {
        value: inlineValue
      },
      Prism.languages.cshtml.tag.inside['attr-value']
    );
    Prism.languages.insertBefore('cshtml', 'prolog', {
      'razor-comment': {
        pattern: /@\*[\s\S]*?\*@/,
        greedy: true,
        alias: 'comment'
      },
      block: {
        pattern: RegExp(
          /(^|[^@])@/.source +
            '(?:' +
            [
              // @{ ... }
              curly,
              // @code{ ... }
              /(?:code|functions)\s*/.source + curly,
              // @for (...) { ... }
              /(?:for|foreach|lock|switch|using|while)\s*/.source +
                round +
                /\s*/.source +
                curly,
              // @do { ... } while (...);
              /do\s*/.source +
                curly +
                /\s*while\s*/.source +
                round +
                /(?:\s*;)?/.source,
              // @try { ... } catch (...) { ... } finally { ... }
              /try\s*/.source +
                curly +
                /\s*catch\s*/.source +
                round +
                /\s*/.source +
                curly +
                /\s*finally\s*/.source +
                curly,
              // @if (...) {...} else if (...) {...} else {...}
              /if\s*/.source +
                round +
                /\s*/.source +
                curly +
                '(?:' +
                /\s*else/.source +
                '(?:' +
                /\s+if\s*/.source +
                round +
                ')?' +
                /\s*/.source +
                curly +
                ')*',
              // @helper Ident(params) { ... }
              /helper\s+\w+\s*/.source + round + /\s*/.source + curly
            ].join('|') +
            ')'
        ),
        lookbehind: true,
        greedy: true,
        inside: {
          keyword: /^@\w*/,
          csharp: cs
        }
      },
      directive: {
        pattern:
          /^([ \t]*)@(?:addTagHelper|attribute|implements|inherits|inject|layout|model|namespace|page|preservewhitespace|removeTagHelper|section|tagHelperPrefix|using)(?=\s).*/m,
        lookbehind: true,
        greedy: true,
        inside: {
          keyword: /^@\w+/,
          csharp: cs
        }
      },
      value: inlineValue,
      'delegate-operator': {
        pattern: /(^|[^@])@(?=<)/,
        lookbehind: true,
        alias: 'operator'
      }
    });
    Prism.languages.razor = Prism.languages.cshtml;
  })(Prism);
}

export { cshtml as default };
